Saturday, May 31, 2014

Code Styling & Good Programming Habits - Phong cách lập trình


Before we begin, this article is for anyone of any skill level. If you're just starting out, this will help you avoid nasty habits that you may pick up now or later. If you're an intermediate level scripter, you're probably relatively capable in terms of producing something that people can use, but may have trouble helping others understand your code properly.


This is an addendum to this post, which I don't feel is complete enough and needed some additions.

This article will teach you what I believe are the best coding styles. I don't even follow all of these because I started out with some bad habits and they are hard to break. All of these are tried and tested but may still be up for debate. Some of these are indisputable while others are highly subjective. For those which I think are subjective, I'll always leave multiple options and state that they are not conventions. So without further ado, here we go.

The Opening Comment Block

The best place to start is, logically, the beginning of the plugin, where you place comments explaining what the plugin is and does. I often ignore this section for small plugins or ones which I'm not going to release. This is, however, good form for a plugin which you are planning on releasing on its own. A typical comment block may look like this:

PHP Code:
/*

Copyleft 2008
Plugin thread: http://forums.alliedmods.net/showthread.php?p=633284

DUEL MOD
========

Description
This mod is designed to allow dueling, where players challenge each
other and engage in battle. It is designed for the mod "The Specialists",
but can be used on other mods.

Commands
say /duel - Will challenge the player being looked at to a duel.
say /accept - Will accept a challenge. Note that you can also use /duel,
but you must be looking at the person who challenged you. With /accept,
the last person to challenge you will be accepted.
say /punchingbag - Turns the player into a punching bag
(requires amx_duel_punchingbag to be set to 1).

Credits
Havoc9 Community - Testing help (specifically SaderBiscut and Jimmy Striker).
Lord_Destros - Testing help.
Steely - Testing help.
sawyer - Testing help.
Frost - Testing help.
coderiz - New semiclip method.
Charming - Encouragement.

Changelog:

    Jun 1, 2008 - v1.0 -    Initial release
    Jun 2, 2008 - v1.1 -    [FIXED] Some repeated variables
                [FIXED] Message printing incorrectly
                [FIXED] Duel off not working properly
                [ADDED] Punching bag mode
                [ADDED] True semiclip
                [ADDED] Attack blocking between
                duelists <-> players
                [ADDED] God mode to normal players
    Jun 4, 2008 - v1.2 -    [ADDED] Deny command
                [ADDED] Pair command
                [ADDED] Name parameter to /duel command
                [ADDED] Glow cvar
                [FIXED] Messages printing incorrectly
 
*/  
This is a strong opening because it does a number of things:
  • It clearly states what the plugin is and does.
  • It clearly states the plugin thread (this is, by far, the most important of all of these).
  • It clearly states which game it works for.
  • It clearly states commands. Cvars are optional, but I typically leave them in the plugin thread to keep it uncluttered in the source code.
  • It clearly states credits. You should always list these both in the source code and in the plugin thread.
  • It has a clear and informative changelog so users know which version they're looking at just by glancing at it.
The copyleft notification is unnecessary but may be considered good form just to remind people that all AMXX plugins are GPL'd.

Your opening comment block can look however you want it to, but the most important thing is that it gets the information out. If you find your style is impeding this, it's probably a good idea to revise it.

Macros and the Preprocessor

This section includes stuff such as your header inclusions (a.k.a. "#include") and compiler definitions (a.k.a. "#define"). There's not a whole lot you can do here in terms of messing up convention, but one of the important things to remember is that all macros should be capitalized from beginning to end. For example,

PHP Code:
#define MAX_ITEMS 10  
... as opposed to ...

PHP Code:
#define max_items 10  
This avoids mistaking macros for variables and is a widely held convention - it is not really up for debate. The only exception to this is for macro function definitions, an example of which being:

PHP Code:
#define MyNewIsUserAlive(%1) is_user_alive( %1 )  
This is acceptable because the macro essentially acts as a function as opposed to a location of data in memory.

Another stylistic consideration is that your header inclusions should be as minimalistic as possible. For example, if you don't use any FakeMeta functions, don't include the fakemeta.inc file as it is useless clutter.

One other important thing you should know about preprocessor styling is that conditions should generally be left untabbed. This makes it very clear where blocks of code will operate and where they won't. For example:

PHP Code:
PunishUserid )
{
#if defined KILL_USER
    
user_killid )
    
client_print0print_chat"[Kill] The user has been killed")#else // KILL_USER
    
user_slapid)
    
client_print0print_chat"[Kill] The user has been slapped")#endif // KILL_USER}  
Some people tab it out to whatever level the code is already at, but I think it's easy to miss that a preprocessor condition is necessary for any of it to run. Both are acceptable but I believe not tabbing out the condition macros is better.

The final thing you should know is that, as in my previous example, it's a good idea to leave a comment at each #else, #elseif and #endif command denoting what exactly it's checking.

Indentation, Tabbing and Spacing

Tabbing should always be done whenever you open a code block (which will be discussed more in detail later). A standard tab is either done using the "Tab" key or using 4 spaces. Less commonly, people use 1, 3 or 8 spaces for their tabs, but this is very rare and generally frowned upon. Almost everyone uses the "Tab" key because then other people can set their tab-size higher or lower in their text editors.

There are also many styles of spacing, but I believe the best (despite my not using it personally) is to add a space whenever you put a parameter in brackets, after a comma, immediately after a condition/loop keyword and before/after any operators which take two parameters on the left and right. For example:

PHP Code:
if ( !cmd_accessidlevelcid ) )  
... or...

PHP Code:
for ( new i10i++ )  
This allows you to easily distinguish functions from conditions/loops and also allows you to easily pick out parameters, operators, etc.

This is by no means standard and is highly up for debate, but to me this makes the most logical sense and is what I would use if I had the option of immediately wiping my habits and picking a new set of them. I would, however, advise you to always add spaces between operators with two parameters and after commas.

You may also consider adding two carriage returns as opposed to one between functions as it may make them easier to read. This is by no means a convention but I recommend it personally.

Case

This is important mostly for variables and functions. I'm putting this here because I'm going to be implying it in my next section, which covers global variables. This section is largely up for debate, but I believe the strongest and most legible way of doing it is what is commonly referred to as "camel case".

There's a slight variant of camel case called "mixed case" which I believe is slightly better. Here's how it goes:

For functions, you always capitalize the first letter of every word. For example:

PHP Code:
public EventDeathMsg()  
With variables that are untagged, you capitalize the first letter of every word after the first. For tagged variables, you capitalize the first letter of every word other than the tag. For example:

PHP Code:
new gMyVariable
new myVariable
new userKills  
In full camel case, you would capitalize the first letter of every word, regardless of whether or not it was the first word in the variable name or not. For example,

PHP Code:
new MyVariable
new UserKills  
The problem with this method is that it doesn't distinguish local variables from functions effectively. That's why mixed case is the best way to case your variables. 

Mixed/camel case more or less necessitates totally avoiding using underscores. For example, this looks really weird:

PHP Code:
new My_Variable  
As such, you should either use underscores and avoid mixed/camel case or use mixed/camel case and avoid underscores.

Also, as noted earlier, macros should be full upper-case.

Global Variables and Type Prefixing

Lots of confusion can come from not knowing that something is global. As such, all global variables should be prefixed with some sort of tag identifying them as such. For example:

PHP Code:
new gMyVariable  
Another way of tagging variables is by using the "g_" prefix. For example:

PHP Code:
new g_MyVariable  
While both are perfectly acceptable, I personally prefer the former slightly more although I use the latter for almost all of my works. It is simply a recommendation of form based on my experience.

You should add a carriage return at the end of every global variable as it makes it easier to search out any variables you need. For example:

PHP Code:
new gMyVariable
new gMyOtherVariable  
... as opposed to...

PHP Code:
new gMyVariablegMyOtherVariable  
Some people also tag their variable names with the type that they are associated with. This idea is highly up for debate and almost everyone has moved away from it as it is relatively archaic (Microsoft used to use it, but then moved away from it). This form of tagging is commonly referred to as "Hungarian Notation" (HN, for short). While I personally believe you should avoid using it, being a user of it myself at one point, it is still important to be able to read and understand. HN goes something like this:
  • g or g_ - globals (this is perfectly acceptable to use although you should avoid the others)
  • p or p_ - pointers (this is also perfectly acceptable to use)
  • i - integers/cells
  • f or fl - floats
  • sz - strings
  • b - bools
  • h - handles (can also be used in place of the pointer tag since either is technically correct)
  • v - vectors (not standard to HN but still useful)
  • fn - function (very rarely used even by people who use HN)
There are many more tags, but these are the only ones you can use in Pawn. Here are a few examples of variables tagged in HN:

PHP Code:
// a global cell
new g_iKills
// a global float
new Float:g_flSpeed
// a bool
new bool:bFlag
// a function
public fnEventResetHUD()
// a pointer
new g_pCvar  
If you're not using full HN, I advise you to use "g" as opposed to "g_", as shown in the beginning of this section.

In terms of a technical analysis, HN for anything other than global variables and pointers doesn't make any sense. Pawn is typeless and therefore incapable of actual data types. Booleans are actually just a meaningless tag and floats are cells which are converted by the virtual machine (AMXX's C++ side) whenever they are needed. There are many situations where HN will make variable casting/detagging confusing.

Code Blocks

A code block is anything that is separated by curly braces. While this may be an imprecise definition since in many code blocks the curly braces can simply be left out, I'm going to be addressing this style anyway.

The first thing I'd like to address is spacing. There are two common ways of spacing your code blocks, one of which I believe is vastly superior. The first is by adding a return before the condition opening a block, the second is not doing this. For example:

PHP Code:
MyFunction()
{  
... or...

PHP Code:
MyFunction() {  
I prefer the first much more as the entering and exit brackets will always line up properly and it just looks cleaner anyway. There's no real reason to do the latter and I can't see it as being easier to read.

Another very important concept in code blocks is whether or not to drop them entirely. Under some very special circumstances, you can actually ignore the brackets denoting a code block. There are two cases which make this acceptable:
  • There is a single function call or operation inside it, even if it itself requires data from other function calls (i.e. log_amx( "%d", get_user_kills( id ) )).
  • There is a single code block inside it, even if it contains a lot more code that does not fit in a single function call.
Here are examples of these:

1.
PHP Code:
MyFunctionid )
    
user_killid )  
2.
PHP Code:
MyFunctionid )
    if( 
is_user_aliveid ) )
    {
        
client_print0print_chat"We are now killing %d"id )
        
user_killid )
    }  
Both of these are perfectly valid and will compile without warnings/errors (context exclusive).

This method of dropping brackets is highly subjective. There are many arguments both for and against each. Dropping brackets allows you to type less code, but including them makes it less ambiguous. It can be argued, however, that ambiguity is resolved by tabbing even if there's no brackets. This style is up to you and either way is perfectly acceptable.

Commenting

Commenting is one of the most important things you can do for your code. Even if you break all of these habits, you can still get away with it if your commenting is good enough.

Large blocks of text outside of functions should be commented using the "/* ... */" operators, as in the first section which covered the opening code block. All other sections should be commented using the "//" operator. The reason for this is that Pawn does not support nested comments, i.e.:

PHP Code:
/*
    this is the first comment
    /* this is a nested comment */
                                               
*/  
Just look at how the code looks from this view. The last "*/" is green, showing that the interpreter doesn't actually understand what's going on here.

If you need nested comments, a little-known way of doing this is by using the preprocessor #if along with the condition 0, i.e.:

PHP Code:
#if 0
    
this is the first comment#if 0
        
this is a nested comment#endif
#endif  
This is, however ugly and is more suited to code which you need to be able to activate and deactivate quickly. As such, you will not see it in production code very often and is generally reserved for debugging.

When commenting, you should keep your comment on each line below 80 characters. This is to prevent the compiler from complaining and is also for people who do not have widescreen displays. The "line below 80 characters" rule applies for any line of code, not just comments. Some people also space their comment out with one extra space (for two spaces in total) after the first line. I believe this makes it more readable, but this is highly subjective.

Your comments should always be as descriptive as possible. You should inform the reader of what you're doing, why and what the result will be. For example, this is a bad comment as it serves no constructive use:

PHP Code:
// Start the for loop.for ( new i7i++ )  
A much better comment would be something like this:

PHP Code:
// There's a strange memory corruption bug in this function.
//  As such, we have to loop through each of the 7 elements
//  in the array and set them to the value that they should be.
for ( new i7i++ )  
Comments should also be placed between large blocks of code (such as functions which are grouped together as they are similar). These can be in the form of titles or just small descriptions. For example:

PHP Code:
//////////////////////////////////
// HELPER FUNCTIONS             //
//////////////////////////////////  
As is implied, it's a good idea to put your functions in an order where they are related to one another. These grouping tags are then much more meaningful.

Whitespace

Whitespace is a very important concept in coding. Although it has technically been covered previously in the "Tabbing and Spacing" section, there is still the issue of when to use carriage returns. If used correctly, whitespace allows people to understand your code more effectively. There are many different styles of using whitespace, but I believe the best is to simply add an extra carriage return whenever you break away from a part of a function which has similar results. For example:

PHP Code:
// Ignore the fact that this function would bear no real
//  useful results when actually used in a server.

MyFunction()
{
    new 
id random_num113 )
    
user_killid )

    
set_pcvar_nump_MyCvar)
}  
As you can see, the cvar setting call has little to do with finding a player and killing them. Thus, we add an extra carriage return to separate it.

Variable and Function Naming

As a rule of thumb, variable and function names should be as descriptive as possible. For example, this is a bad variable name:

PHP Code:
new temp  
It doesn't tell you anything about it other than the fact that it's more or less temporary (although, more often than not, this variable is pivotal to the operation of the function and may be used as part of or the return). A much more descriptive variable name would be something like this:

PHP Code:
new KillsSinceLastSpawn  
This tells you everything you need to know about it while also making you immediately realize that it's probably an integer (cell).

For functions, it is generally advised to follow the same rule except in the situation where you're creating a hook. Often, you should tag your hook callbacks with what type of callback they are. For example, an event might be tagged with "Event" or a forward with "Forward". This can be seen as follows:

PHP Code:
register_forwardFM_Spawn"ForwardSpawn" )  
... or...

PHP Code:
register_event"DeathMsg""EventDeathMsg""a" )  
It matters little what tag you use (many people use ev, Fwd, etc.) but it is generally advised to tag these functions as it distinguishes them from utility functions.

Extras

This section contains things that didn't really fit into other sections.

For starters, you should always use constants whenever they're provided. For example, use:

PHP Code:
return PLUGIN_CONTINUE  
... as opposed to...

PHP Code:
return 0  
This makes it much easier to get into the habit of using your own constants which you have declared in your plugin, which is also a good practice.

Semicolons are useful if you plan on moving to C/C++/PHP or any related language, but are often misused (for example, most people don't know do while loops have to be ended with one) or are passively used. Being a C and other related languages programmer myself, I have no trouble switching between using and not using semicolons. Since it's a convenience Pawn affords, I take it and don't use them. You can force semicolons using the following:

PHP Code:
#pragma semicolon 1  
Sometimes, when writing an API or header, you will have to document your functions so others can understand them. Doxygen (a source code documenting software package) uses a specific format for this, which AMXX has started using since SQLx was introduced. This is probably the best way to do it simply because it's the most readable and can be fed to Doxygen later. Here is an example of a Doxygen-documented utility function:

PHP Code:
/**
 * Print command activity.

 * @param id           Calling admin's id
 * @param key[]      Language key
 * @param any:...    Arguments to the format function (w/out the id/lang key)

 * @return    True on success, False on fail
**/

stock UTIL_PrintActivity(const id, const szLangKey[],any:...)  
Finally, sometimes in a collaborative project you will have difficulty identifying who did what parts. For this reason, I believe it's best to comment your code as follows:

PHP Code:
// Hawk552: Added this function to gather and store player
//  names, then sort them alphabetically.

SortNamesNames[][], NumNamesMaxLength )  
Conclusion

In conclusion, even if you ignore most of these tips, I hope you follow at least some of them as you will find them invaluable as you move along in the process of learning how to code. Ideally, you should follow all of these, but many people find them difficult to use or not as clean as their alternative.

As I think of more stuff to add, I may do this, but for the most part I believe this tutorial is complete. As always, if you have questions or comments, feel free to post or PM me.

EDIT: I bit the bullet and decided to actually learn to use the style that I advocate here. I have posted a plugin that uses this here:
http://forums.alliedmods.net/showthread.php?t=102839

0 nhận xét:

Post a Comment