Raised This Month: $32 Target: $400
 8% 

Good programming habits


Post New Thread Reply   
 
Thread Tools Display Modes
Author Message
Ryan
Senior Member
Join Date: May 2004
Location: NH, USA
Old 10-04-2004 , 01:52   Good programming habits
Reply With Quote #1

ATTN: ADMINS - THIS MAY BE WORTH A STICKY

Since the release of my source code for Warcraft 3: Expansion, I've gotten a lot of praise from people saying things like "wow that's the cleanest code I have ever seen!", and "where did you learn to code like that?", and my favorite, "that must have taken you forever to get your code that neat", etc etc...

...but this is simply not the case.. If you adapt to good programing habits at the start of a project, your code will look clean and professional at the very end as well. And that's what I'm here to help everyone with today.

Now let me start by saying that I've looked at the source code to lots of plugins, and overall they are very sloppy. This is not to say they were not coded well, more that they could have been layed out better. This is helpful when admins try to alter bits and pieces for their server(s), or if someone is looking to revamp or convert a script over to another platform.

-- (BEGIN BABBLING) --

I'll let you all in on a secret.. I used to play War3mod (by spacedude) and i LOVED it, however, crit nades, teleportation, and levitation really hurt the server(s) I played on because (especially with levitation and teleport) these skills were used and abused by the players, and there were not many admins available at peak times. My intention was simply to modify these skills so that they re-balanced the game while keeping it fun, but I took one look at the source code and decided I NEEDED to write it from scratch. So I did. And so WAR3X was born.

-- (END BABBLING) --

So let's get down to it. Below is a list of things that can be done to improve your programing habits, which will make them easier for you and others to edit in the long run.
  • Meaningful Variable Names (MODIFIED)
    Many people have a problem with variable names. I've seen variables hit with such a bad case of shorthand that by looking at them, it is a mystery where they came from and what they do. Don't be afraid to use more than 10 characters in your variable names! The key is READABILITY!

    One good habit to get into is using Hungarian Notation (HN) where applicable. For example, all global variables should start with "g_". This will let anyone know that looks at your code that "hey, this is a global variable!", without needing to see where and how it was initialized.

    It is also helpful to prefix any variable that will be used in mathematical equasions with HN, ie- integers with "i", floats with "f", etc, etc. Even though Small is different from other languages in that every variable, no matter the "label", is actually an integer (even floats!), by using a little bit of HN you're almost guaranteed never to get a tag mismatch warning on your compile.

    Don't overdo it, though!!! The key once again is READABILITY. If you don't know which HN to prefix with, then don't bother. Come up with your own naming convention. A good example is player indexes. using "id" is great. but if you're using 2 or more different player indexes in a bit of your code, differenciate them a bit, like "attackerId" and "victimId" or "killerId", etc.

    Remember the phrase KISS - Keep it simple, stupid!
  • Naming Schemes
    Good naming schemes are always a sight for sore eyes. Prefixing similar functions/variables with a common word makes for easy reading and editing of source code.

    For example, If you have in your script events for damage and death, avoid naming them Damage_Event() and Death_Event(). Instead, name them Event_Damage(), and Event_Death(). For text editors that have function lists (Ultraedit32 is what I use), these will appear right on top of each other, adding to the organization of the plugin.

    The same goes for variables. If you are trying to store a player's current health and armor as global variables, try naming them something like g_iPlayerHealth and g_iPlayerArmor.
  • Multiple Files
    Multiple files will help you keep your plugin(s) very organized. This is not necessary for small scripts, however, when your script begins to grow very large, this is definately a good idea. You'd be amazed at how much throwing all of your events into their own file really cleans up your code.

    There is one minor problem with combining files, though, and that is the inaccurate display of runtime errors (if they exist). If your script is made up of 5 files, and in the 5th file there is a runtime on line 10, AMXX will report your error on line 10, even if there are 500 lines of code in file 4 alone.

    To fix this problem for war3x, Willyumyum came up with an INLINE FILE COMBINER, which combines all of our inline files into one large file, which we then compile. This program will be made public, complete with a nice looking GUI frontend, within the next month or so
  • Use constants
    Another thing you want to avoid while programing, is hard-coding things into your scripts. Use constants, and #defines instead. Even if something is used only once in your plugin, it might be worth making a constant for it. Why? because constants take normal things, like numbers, and make them into something named, which are far easier to remember. They also make changing an aspect of your plugin as easy as changing one constant, instead of having to change EVERY occurance of the number '3' in your code, for example.

    Here's another example... You want to write a plugin that acts like an RPG, and will have 4 races, with 3 classes per race. You want to save a couple of constants like so:

    Code:
    #define TOTAL_RACES   4 #define TOTAL_CLASSES 3
    This will make performing loops much easier, and if you decide to add an additional race in the future, All you'd need to do is change TOTAL_RACES to 5.
  • Make extensive use of comments!
    Even though you may know every inch of your plugin, a foreign set of eyes may not, and as your script gets bigger, you may forget about things yourself.

    If there is ever something you can not finish, or don't quite know how to implement, a comment can be a useful tool. Putting one in your code may catch a fellow coder's attention that has a solution to a problem you may be having, etc etc. For example..

    Code:
    /* for some reason, this sequence screws up if this     variable is not initialized to 0 */ new iVariable = 0;
    This could prevent another programer from looking at your plugin and removing this seemingly redundant initialization, in turn causing untraceable problems in the plugin down the road. So do yourself a favor, and add the comments in. You will thank yourself in the long run.
  • Close with semicolons (MODIFIED)
    I want to emphasise that this is NOT REQUIRED by the Small language, but a good habit to get into is closing out your lines with semicolons. This is because if you have any intentions of learning any other language, you're almost guaranteed that you will need to close out your lines with them. So again, not required, but overall a good habit.

    Code:
    /* For people that want to force the compiler to look for closing semi's,     you can force them with the following: */ #pragma semicolon 1
  • Conformity (NEW)
    If you pick a style, stick with it. Don't make multiple coding standards in one document. (From: BAILOPAN)
  • Indentation, PLEASE!
    I've seen a LOT of scripts that do not do a single bit of indentation. This is NIGHTMARE to debug!!! Every time you open a brace "{" INDENT! this means at the start of functions, the start of if/then conditionals, switches, etc, etc, etc, etc, etc, etc, etc, etc, etc! Please no more of this!...

    Code:
    public some_function(id){ new szVariable[10] format(szVariable,9,"hello!!!!") if(id==0){ server_print(szVariable) }else{ client_print(id,print_chat,szVariable) } return PLUGIN_HANDLED }
    Take the time to make it READABLE! Like so..

    Code:
    public some_function( id ) {     new szVariable[10];     format( szVariable, 9, "hello!!!!" );     if ( id == 0 )     {         server_print( szVariable );     }     else     {         client_print( id, print_chat, szVariable );     }     return PLUGIN_HANDLED; }
  • Whitespace, Whitespace, Whitespace!!!
    This I saved for last because it is the MOST IMPORTANT!!! whitespace does not add to the size of your compiled code (because it is overlooked by the compiler), but adds a whole new dimension of readability to your plugin. Avoid clumping things together. Use common sense and separate things from each other. This means adding spaces after every function argument, indents after every conditional statement, and linefeeds to separate unrelated actions. Also use comments, and be sure to space out all arguments inside of parenthesis ()'s. (see example above).

    I'm going to use a function right out of one of the default .sma files that come with both AMX and AMXMODX, originally written by OLO.

    Original Code

    Code:
    public cmdSlay(id,level,cid) {   if (!cmd_access(id,level,cid,2))     return PLUGIN_HANDLED   new arg[32]   read_argv(1,arg,31)   new player = cmd_target(id,arg,5)   if (!player) return PLUGIN_HANDLED   user_kill(player)   new authid[32],name2[32],authid2[32],name[32]   get_user_authid(id,authid,31)   get_user_name(id,name,31)   get_user_authid(player,authid2,31)   get_user_name(player,name2,31)   log_amx("Cmd: ^"%s<%d><%s><>^" slay ^"%s<%d><%s><>^"",     name,get_user_userid(id),authid, name2,get_user_userid(player),authid2 )       switch (get_cvar_num("amx_show_activity")) {     case 2: client_print(0,print_chat,"%L",LANG_PLAYER,"ADMIN_SLAY_2",name,name2)     case 1: client_print(0,print_chat,"%L",LANG_PLAYER,"ADMIN_SLAY_1",name2)   }          console_print(id,"[AMXX] %L",id,"CLIENT_SLAYED",name2)   return PLUGIN_HANDLED }
    Polished Code

    Code:
    public cmdSlay( id, level, cid ) {   if ( !cmd_access( id, level, cid, 2 ) )     return PLUGIN_HANDLED;   new szArg[32];   read_argv( 1, szArg, 31 );   // Get ID of player to be slayed   new targetId = cmd_target( id, szArg, 5 );   if ( !targetId )     return PLUGIN_HANDLED;   user_kill( targetId );   // Grab admin/target names/authids   new szAuthid[32], szName[32], szAuthid2[32], szName2[32];   get_user_authid( id, szAuthid, 31 );   get_user_name( id, szName, 31 );   get_user_authid( targetId, szAuthid2, 31 );   get_user_name( targetId, szName2, 31 );   // Log command     log_amx( "Cmd: ^"%s<%d><%s><>^" slay ^"%s<%d><%s><>^"",     szName, get_user_userid( id ), szAuthid, szName2, get_user_userid( targetId ), azAuthid2 );       // Display slay to all other players   switch ( get_cvar_num( "amx_show_activity" ) )   {     case 2: client_print( 0, print_chat, "%L", LANG_PLAYER, "ADMIN_SLAY_2", szName, szName2 );     case 1: client_print( 0, print_chat, "%L", LANG_PLAYER, "ADMIN_SLAY_1", szName2 );   }      // Confirm slay to admin       console_print( id, "[AMXX] %L", id, "CLIENT_SLAYED", szName2 );   return PLUGIN_HANDLED; }

I hope this information is helpful to everyone that wants to clean up their code a little bit! (or a lot)

Take care
-Ryan
__________________
Warcraft 3: Expansion
Homepage | Downloads | Forums
Ryan is offline
Send a message via AIM to Ryan
BigBaller
Veteran Member
Join Date: Mar 2004
Location: Everett, WA
Old 10-04-2004 , 01:59  
Reply With Quote #2

I find it very informative. Good job writing that all up. I think this is worth making this a sticky.

Quote:
To fix this problem for war3x, Willyumyum came up with an INLINE FILE COMBINER, which combines all of our inline files into one large file, which we then compile. This program will be made public, complete with a nice looking GUI frontend, within the next month or so
That also sounds very interesting
__________________

BigBaller is offline
BAILOPAN
Join Date: Jan 2004
Old 10-04-2004 , 03:07  
Reply With Quote #3

I agree with almost everything. You need to add a few points, other than that, it's a good write up.

Conformity - If you pick a style, stick with it. Don't make multiple coding standards in one document.

Close with semicolons - this is unnecessary, and semicolons are pointless (other than a cheap way for the compiler to detect the end of a line). Small was designed so that semicolons are not needed, and from a scripting standpoint, this is acceptable.

Hungarian Notation - This is a menace to readable code and should never be used without careful consideration. You will find many flame wars on the internet about Hungarian Notation. Invented by Microsoft, it's also one reason the Windows API is so cryptic and doesn't conform to its own naming scheme.

Let's say you have a global pointer to function... g_pfn? What about a class member that's a float pointer? m_fp? Let's get more complicated... member of a class, static pointer to a null terminated string. m_spsz? What does lpctstrX mean? Then if you change the type, you have to rename everything. For example in the Windows API, "wParam" changed to a double-word in win32, but they never renamed it to "dwParam".

In Small, you can have "tags" or you can use meaningful variable names. Why would you ever use something like "g_aNum"? Use "TotalAccounts". The meaning is unmistakable, and you won't confuse it with a temporary variable name. At the very worst, you can attach "g" for "gTotalAccounts" but this just construes the meaning.

Furthermore, Hungarian notation is only useful in a typed language (and even then it is a redundant comment). Small is a typeless language and therefore it should never be seen. This holds true for truly object oriented languages, like Ruby - there is no set form of data, and therefore types are abstract. Small only has "cells" which can be "tagged". For example, iFrags is meaningless because Frags is always stored as an integer, even if it's a float!.

If you give variables meaningful names, Hungarian is pointless. What's better, "g_sqlConn" or "SqlConnection"? The only instances in which hungarian notation should be used in small is with floats and vectors, and you should prepend either a "v" or an "f", unless you give a meaningful name like OriginVector or Gravity (because gravity is always a float). Another instance is scope, but in Small that's basically irrelevant (except for "g" if you can't make good names).

Another example: what's the best choice for an iterative variable?
Hungarian notation: iI (the variable is i, with hungarian added on)
Normal coding: i
Readable: index or count

Another example: You used szName. Why use the "sz", since when is a Name anything other than a string?

I won't sticky this with Hungarian notation listed as I think I'd throw up if everyone started using it ;] I know I threw up after reading pages and pages of OLOcode. Okay that was a long rant :] I hope I didn't come off as too harsh.
__________________
egg
BAILOPAN is offline
Ryan
Senior Member
Join Date: May 2004
Location: NH, USA
Old 10-04-2004 , 03:33  
Reply With Quote #4

I agree with you there, Bailo, to some degree.

hungarian notation
i realize that about 90% of all hungarian does not apply to small because small does not have pointers, classes, members, etc etc.. however, there are still plenty of places it can be used effectively.

I dont know how many times I've browsed the forums finding someone requesting help with their plugin, only to find it was a simple tag mismatch between an int and a float, or some other data type. The point is, starting all floats with "f", and strings with "sz", and ints with "i" is good for the programmer, because immediately you know whether or not you need to convert one of your variables into a required form for a function. One simple letter does not throw off readability, and it doesnt matter how small stores the variable, because regardless, to avoid any mismatch warnings one needs to make sure all variable types match function prototypes. And that is all I'm getting at.

I realize the more you get into it, readability does become more of a priority. For example, even in my plugin, i use a g_PlayerInfo[] array that holds all of a player's stored information. And my #defines hold no conformity to the hungarian notation. Player indexes are another thing i opted for readability for..

So I guess i stressed a bit too much on the HN, and I'll edit my post to make it less 'crucial' sounding. Use your best judgement on when and where to use these. Any variable that people may confuse data types with should use it, at least.

close with semicolons
I think i mentioned that this was not a requirement of small. This thread was not aimed strictly for the small language, but moreso for any programming language. Many people that write in the small language will probably move on to other languages like c and c++ in the future, which both require closing semi's, and as also mentioned, it's just a good thing to get into doing. Some people use a #pragma to force semi's for small, and if you are unfamiliar with them, you will be very confused as to why you have 20+ errors on your compile.

conformity
I agree here 100%
__________________
Warcraft 3: Expansion
Homepage | Downloads | Forums
Ryan is offline
Send a message via AIM to Ryan
BAILOPAN
Join Date: Jan 2004
Old 10-04-2004 , 04:38  
Reply With Quote #5

Quote:
I dont know how many times I've browsed the forums finding someone requesting help with their plugin, only to find it was a simple tag mismatch between an int and a float, or some other data type.
Well, this shows poor thought process in general, which isn't a case for hungarian notation ;]

Again Small does not have data types - everything is an integer (floats too), even if it's tagged (tags just help with abstraction). If a person prepends "f", they shouldn't be thinking "prepend 'f' to all floats", they should be thinking, "make sure that someone reading the source knows that this variable holds a float value".

The important thing is to shift the concentration from "Hungarian Notation" to "Meaningful Variable Names", which is far more important when you see code like this:
Code:
while(*src++=*dst++);
Which was an actual line of AMX Mod code where hungarian notation does nothing ;]

After all, it's just a comment, and comments are more valuable than en/de cyphering variable codes.
__________________
egg
BAILOPAN is offline
johnjg75
Veteran Member
Join Date: Mar 2004
Location: Delaware
Old 10-04-2004 , 05:11  
Reply With Quote #6

yea, these are things that i usually do but i really need to get in the habit of using g_ and ivar. This is helpful especially for scripters who are new to the community. I am also working on a long script myself and i use external files . Thanks for the time to write all this and good job
__________________
johnjg75 is offline
Send a message via AIM to johnjg75 Send a message via MSN to johnjg75 Send a message via Yahoo to johnjg75
Timmi the savage
Senior Member
Join Date: Jul 2004
Location: seattle
Old 10-04-2004 , 06:09  
Reply With Quote #7

Very nice. Thanks for this bit of information. This is very helpfull. This makes very good sense. Im going to re-write my current plug to fit along these lines. And probally any others I may write in the future.

---Excelent---
Timmi the savage is offline
Send a message via Yahoo to Timmi the savage
PM
hello, i am pm
Join Date: Jan 2004
Location: Canalization
Old 10-04-2004 , 13:21  
Reply With Quote #8

I always close with semicolons. It looks so much better.
And btw, always write this:
Code:
while(*src++=*dst++);
as:
Code:
while(*src++=*dst++) /* nothing */;


Hungarian notation:
I agree with g_, (s_, m_ where supported) but i wouldn't like to write:
ms_szparrMyArray

To some degree, it's useful, but should not be overused ;)
BAILOPAN, g_ makes clear to everyone that the variable is global, and inventing good names won't help there in my opinion
__________________
hello, i am pm
PM is offline
Ryan
Senior Member
Join Date: May 2004
Location: NH, USA
Old 10-04-2004 , 13:41  
Reply With Quote #9

Quote:
Originally Posted by BAILOPAN
The important thing is to shift the concentration from "Hungarian Notation" to "Meaningful Variable Names"
Ahh, this is a good point, and moreso what I was getting at. Let me update the thread now.
__________________
Warcraft 3: Expansion
Homepage | Downloads | Forums
Ryan is offline
Send a message via AIM to Ryan
Ryan
Senior Member
Join Date: May 2004
Location: NH, USA
Old 10-04-2004 , 14:22  
Reply With Quote #10

THREAD EDITED

please re-consider for STICKY
__________________
Warcraft 3: Expansion
Homepage | Downloads | Forums
Ryan is offline
Send a message via AIM to Ryan
Reply


Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT -4. The time now is 10:19.


Powered by vBulletin®
Copyright ©2000 - 2024, vBulletin Solutions, Inc.
Theme made by Freecode