CodeStyleGuide

From TDN


Coding Guidelines for Torque Game Engine



The following establishes a set of guidelines for the submission of source code to GaraqeGames products. These guidelines are aimed at C++ code, but should be applied to script and other forms of code where appropriate. The goal of the guidelines is manifold:

  • Easy to read and understand code.
  • Prevent bugs.
  • Good architecture and modularity.
  • Cross-platform and cross-compiler compatibility.


For related information, see the CodingBestPractices topic.

Contents


File Architecture

    The standard order for the parts of a code file is as follows:
    1. File Header Comment
    2. #includes
    3. #defines
    4. Local structures
    5. Static local variables
    6. Static function prototypes
    7. Static and global functions
    8. Class methods
    9. Console Exposed Class Methods
  • All files must begin with a ‘File Header Comment’. The format for this header is as follows:
    //-----------------------------------------------------------------------------
    // Torque Game Engine
    // Copyright (C) GarageGames.com, Inc.
    //-----------------------------------------------------------------------------
    
  • Structuring all code in the engine in the same way tells the reader that the code was prepared by a professional programmer who is not discovering how to organize a code file for the first time. It is particularly important that code produced by GarageGames has a professional look to it since it is intended for public consumption. A consistent professional look also makes it easier to find the key parts of a code module.
  • The coder should understand how the compiler interprets the #include directive. Brackets (<>) are intended for system library header files, whereas quotes (“”) are intended for user header files. When Brackets are used, directories are searched in the order specified by the makefile or build environment for the compiler being used. The compiler will use the first occurrence of the header file found. When quotes are used, system directories are searched first, followed by user libraries. If a header file is found in more than one directory, the last occurrence is used. Finally, Torque requires the full path to include files be specified. So, "platform/platform.h" not just "platform.h".
  • Avoid exposing member variables publicly. If the class needs to expose the variable’s state then an accessor function should be created. If an accessor function does nothing but return a variable, it should be inlined since the resulting code will be faster (but not necessarily smaller.).
  • Avoid using global variables. Global variables are too easy to be changed unwittingly by another class and make debugging extremely difficult. If there truly is a need for global access to the value, a special class should be used to wrap the value and at least give someone debugging an easy way to see when the value is being changed. These variables should be declared as private members which prevents them from being used without the accessor function. In reality, you’ve probably done something wrong.
  • Functions used only from within a single file (this does not include methods of a class), should also be declared as static so that they are not global in scope. Once again, you’ve probably done something wrong.
  • To prevent recurring inclusion of header files, all header files have an #ifndef directive immediately following the File Header Comment, and a corresponding #endif directive as the last line of the header file. The following example shows the proper format and nomenclature for a header file called BaseClass.h
          #ifndef      _BASECLASS_H_
          #define      _BASECLASS_H_
          .
          // rest of header file for baseclass.h
          .
          #endif      // _BASECLASS_H_
    

Comments

  • Use the "// comment" notation rather than the "/* comment */" notation. The later style of comment leads to confusion when comments are accidentally nested. Furthermore, since the "/*" and "*/" can span multiple lines, it is difficult to pair up the starting and ending points of the commented code (one common error is to think that two disjoint comments are actually one single comment).
  • Separate function groups (a number of related functions) in header files with a comment block such as the following:
          //-------------------------------------------------------------------------------
          //   This set of functions perform rendering operations on the object
          //-------------------------------------------------------------------------------
    
  • Mark function groups inherited from a parent class as such:
          //-------------------------------------------------------------------------------
          //   SceneObject derived methods
          //-------------------------------------------------------------------------------
    
          // transform interface
          void setTransform(const MatrixF&);
    
          // rendering interface
          void renderObject(SceneState*, SceneRenderImage*);
          bool prepRenderImage(SceneState*, const U32 stateKey, const U32 startZone,
                                    const bool modifyBaseZoneState = false);
    


  • Precede each function in a code file by the following comment block:
          //-------------------------------------------------------------------------------
          // Function name:  MyObj::myMethod
          // Summary:        This is what my method does.
          //-------------------------------------------------------------------------------
    


  • The purpose of this comment is to allow the reader to quickly locate the beginning and end of functions within the file and to document the functions purpose. It is amazing what a sentence or two description per function will do for the readablity of code a year down the road.
  • Torque's C++ code is documented using Doxygen. Read the Doxygen manual. We use the triple slash documentation standard, which means that all our documentation related header comments have a triple slash instead of a double slash. All you have to do is make sure the comment is near (or after, if you use ///<) the appropriate item and Doxygen will automatically put the text of your comments into the output. Torque source code contains lots of documentation examples.

Tabs

  • You should never use Tab characters in any code file. Tab spacing is dependent on the application or printer used to view the file and almost always results in misaligned code. It is your responsibility to ensure that your code editor is not introducing Tabs into the source code.
  • Tab stops are to be set at 4, 7, 10, …(i.e., 1 tab = 3 spaces). It is your responsibility to ensure your editor is inserting three spaces for each tab stop.

Variable Declarations

"The most important consideration in naming a variable is that the name fully and accurately describes the entity the variable represents. An effective technique is to state in words what the variable represents. Often, that statement itself is the best variable name" (Code Complete, chapter 9, pp 185).

  • Variables should have one purpose in a program. Do not use 'i' as an index entry and then use the same variable to calculate the number of stars in the galaxy.
  • Do not use intrinsic types. All of the variable types have been replaced to allow for cross-platform compiling.
          Use    Don't use
          F32    float
          F64    double
          S32    int
          S16    short
          S8     char
          U32    unsigned int
          U16    unsigned short
          U8     unsigned char
    
  • Global variables are prefixed with lower case g:
          gConsole->printf();
          gManager = NULL;
    
  • Member variables of non-data classes are prefixed with lower case m
          const char * mObjectName;
          S32 mType;
    
          // this is a data class, s m prefix not needed
          class Point3F
          {
             F32 x,y,z;
          };
    
  • Static member variables prefixed with lower case sm
          static S32 TeddyClass::smObjectCount;
    
  • Use only the prefix characters defined here. Do not make up your own prefixes to use in addition to these.
  • All non-constant variables are in lower CamelCase (starts with a lower case letter and the first letter of all subsequent words in the variable name are capitalized -- thisIsInLowerCamelCase). Do not use underscores "_" to separate words in variable names. Local variables do not require a prefix but must start with a lower case letter.
          S32   mMemberVariable;   // good member variable
          F32   volumeLevel;       // good local variable
    
  • All constant variables are entirely upper case letters. Words in the variable are separated by underscores ‘_’.
          #define   MAX_FILE_SIZE 256
          const S32 MAX_ELEMENT_COUNT = 128;
    
  • The preferred method of declaring constant variables is to use const declarations. If #define macros are used, a type identifier should accompany the value. This is to prevent ambiguous interpretation of the constant type. Example:
          #define      MAX_POWER_LEVEL   10.0f
          #define      NUM_POWER_LEVELS   ((S32) 5)
    
  • Avoid the use of #define macros that perform a function. The preferred method is to create an inline function.
  • Declarations of pointers to variables are to have the asterisk placed next to the variable name not the variable type. Example:
          char      *charPtr, *name;     // preferred method
          char*      charPtr, name;      // hmmm...a pointer to char and a char.
    

Function Declarations

  • Member function names are in lowerCamelCase.
          void MyClass::myMethodForThisClass()
          {
          }
    
  • Non-member functions can be either local or global in scope. A function that is used only within a given file should be declared as a static function so that it has a local scope. A function used outside the file should be declared as extern in the header file and should have the first letter of it's name capitalized to distinguish it from local functions.
          // good name for global function
          Time GetCurrentTime() 
          {
          }
    
          // good name for local (static) localfunction
          static Interior & getNextInterior() 
          {
          }
    

Brackets and Indentation

  • Brackets must be matched in the same column. Example:
          if ( flag )
          {
             for ( int i=0; i<10; i++ )
             {
                // some code
             }
             // some code
          }
          else
          {
             // some code
          }
    
  • All indentation aligns itself along one of the tab stops (columns 4, 7, 10,..., see earlier section on Tabs for details).
  • Single line statements after a for, if, or while statement must be on their own lines. It makes it much easier to trace and debug when you can tell if the statement was executed.
          if ( obscureCondition == true )  obscureVar += strangeVar;   // breakpoint this?
    
          if ( obscureCondition == true )
             ObscureVar += strangeVar;                                 // breakpoint here!
    

Compiler Warnings

  • Get rid of compiler warnings in your code.
  • Caveat: with some compilers and some compiler settings this is almost impossible; especially for a multi-platform game engine, the jury is still out as to whether one can produce warning free compiles. This caveat aside, some common warnings have equally common methods of removal. If you absolutely know that a line of code is correct but cannot remove a warning for that line, then ask people for help in trying to get rid of the warning.

Some common warnings with common solutions:

  • Signed/Unsigned warnings. Often code will accidentally mix signed and unsigned integers. A container.size() function might return an unsigned integer, while it is being compared to an 'int'. This doesn't cause a problem until that 'int' is a negative number:
          void insertAt( Container container, int location, int value ) 
          {
                if (container.size() > location)
                {
                      return; // can't insert
                } else {
                      container[location] = value;
                }
          }
    

    if "location" above were a negative value (perhaps -1 means 'append' in near-by code, but not here), that first safety test will always fail. There are a number of ways to make this safe.

    1. Change the 'location' parameter to be an unsigned int as well.
    2. make sure that 'location' isn't negative in the sanity test, and then confidently cast the int to 'unsigned int' everywhere the compiler mentions the warning.
    The first solution forces correctness (or moves the problem') to other code, and is better or worse depending on how you see it.

    One of the barriers to using 'unsigned' when appropriate is "typing hassle". A typedef will quickly remove this impeddiment. MS headers define a UINT already, adding something similar to your own code is trivial.

    Within Torque's confines, you'd be well advised to use torque's own integer types: U32 and S32. This won't be an option in some cases (external libraries), but should prove helpful in a wide variety of situations.
  • Unused parameter warnings.
          The following produces unused parameter warning:
    
          void myFunc( S32 usedVariable, S32 unusedVariable )
          {
          }
    
          Do this instead:
    
          void myFunc( int usedVariable, int )
          {
          }
    
          Or:
    
          void myFunc( S32 usedVar, S32 unusedVar1, S32 unusedVar2 )
          {
             unusedVar1, unusedVar2;   // gets rid of compiler warning
          }
    
  • Possible assignment error warnings.
          This code produces a possbile assignment error warning:
    
          S32 a;
          if ( (a=size) )
          {
             // do something
          }
    
          Do this instead:
    
          S32 a;
          if ( (a=size) != 0)
          {
             // do something
          }
    
          Or even better:
    
          S32 a = size
          if ( a )                         // or even: if (a != 0)
          {
             // do something
          }
    

    Of course, the above might even have been an error, and the warning was a good one. Don't force the compiler (and reader of your code) to guess your intent.