Overview
The purpose of this code standard is to set some common ground rules for Allacrost developers. These rules are created to prevent hard-to-find bugs from popping up, to keep the coding constructs safer and less error-prone, and to avoid frustration from dealing with different naming schematas. The following sections are written to be as brief and concise as possible to keep the verbosity of this document down. Below is a short descriptor of what information can be found in each of the sections that follow.
I. Files and Directories
How to name header and source files and in what directories they belong. How to appropriately split up large source files into properly named, smaller files. Usage case of how to include Allacrost header files.
II. Naming Conventions
The rules for naming of classes, functions, variables, members, and constants. Includes naming of special constructs, such as those used for debugging.
III. Namespaces
How to properly name and use Allacrost namespaces. Definition of a private embedded namespace and what types of data should be contained within namespaces.
IV. Coding Style and Conventions
How to properly indent and otherwise write source code.
V. Forbidden C++ Constructs
C++ constructs that are either strictly forbidden or not typically seen in Allacrost source code.
VI. Error Handling
Implementation standard for communicating detected errors with other parts of the system
VII. Commenting
Strict guidelines on how to satisfactorly comment both header and source files.
VIII. CVS Usage Policies
Rules on how to properly access the Allacrost CVS repository to avoid usage problems.
IX. Rationale
Verbosely explains the reasoning behind many of the code standards set in sections one through eight.
I. Files and Directories
- Header files have a .h suffix.
- Source files have a .cpp suffix.
- Do not put spaces in your filenames.
- Filenames should be simple and typically only one word. For example, audio.cpp for the audio engine.
- Regular game header and source files belong in allacrost/src/.
- Header and source files for the video engine belong in allacrost/src/video/.
- Header and source files for the map editor belong in allacrost/src/map_editor/.
- If you need to split a file into mutliple files, make it obvious that the two belong together. For example, map.h split into map_objects.h.
- The only exception to this rule is if all of your code is contained in an exlusive directory. For example, files in src/video/ and src/map_editor/ can have multiple different names.
- To include an Allacrost header file #include "file.h". You do not need to specify a pathname.
- The following must be placed at the top of every Allacrost header and source file.
///////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2004, 2005 by The Allacrost Project
// All Rights Reserved
//
// This code is licensed under the GNU GPL. It is free software and you may
// modify it and/or redistribute it under the terms of this license.
// See http://www.gnu.org/copyleft/gpl.html for details.
///////////////////////////////////////////////////////////////////////////////
II. Naming Conventions
- Classes should contain only lowercase and uppercase letters. The first letter of each word in a class name shall be capitalized (CamelCase).
class GameSettings() { ... }; - Constants should consist of all uppercase letters, underscores, and numbers.
const uint32 MY_GAME_CONSTANT0 = 0; - Enums should consist of all uppercase letters, underscores, and numbers (both the enum type and its values). There should also be an invalid member (equal to -1) and a total member (used for iterating through all possible assignment valuse). All enum values need to be specifically assigned a value as well.
enum ARMOR_UPGRADE_TYPE { ARMOR_TYPE_INVALID = -1, ARMOR_TYPE_LEATHER = 0, ARMOR_TYPE_CHAIN = 1, ARMOR_TYPE_TOTAL = 2 }; - Functions should consist only of lowercase and uppercase letters, where the first letter of each word in the function name is capitalized (CamelCase).
bool GameInput::ConfirmState(); There are three exceptions to this rule:- All private class functions are prefixed with an underscore.
void MapMode::_GetDrawInfo(); - A function used strictly for debugging purposes must begin with DEBUG_, and be further prefixed with an underscore if it is a private class function.
void GameModeManager::DEBUG_PrintStack(); - A temporary function that will eventually become defunct must begin with TEMP_, and be further prefixed with an underscore if it is a private class function.
void MapMode::TEMP_CreateRandomMap();
- All private class functions are prefixed with an underscore.
- Variables and class members should be all lowercase letters, underscores, and numbers.
uint32 tile_rows = 50;- All private class members must be prefixed with an underscore.
bool MapMode::_random_encounters;
- All private class members must be prefixed with an underscore.
- Class member access functions should follow the following conventions, in addition to the conventions for normal functions.
- Functions that read the class member should be prefixed with Get
float GameAudio::GetMusicVolume();- The only exception to this is for boolean class members, in which case the function should be prefixed with Is
bool GameVideo::IsStatic();
- The only exception to this is for boolean class members, in which case the function should be prefixed with Is
- Functions that modify the class member should be prefixed with Set
void GameAudio::SetMusicVolume(float volume); - For simple data types (booleans, integers, characters, floats, strings), the Get and Set functions should pass arguments by value and return by value.
- For complex data types, void should be returned and the argument should be pass by reference.
- Naming of functions should be done via a CamelCase conversion of the class member in question, removing the prefixing underscore of class members if necessary. For example:
class GameAudio {
private:
float _music_volume;
public:
float GetMusicVolume() { return _music_volume; }
}; - Exceptions to these conventions may be made in special cases.
- Functions that read the class member should be prefixed with Get
III. Namespaces
- All headers and source files must contain all of their code and constructs within a namespace.
- The only file that is an exception to this rule is main.cpp.
- The using namespace directive may only be used in source files, not header files.
- Namespaces must match the filename and be prefixed with hoa_. For example, audio.h has the namespace hoa_audio.
- Code that is split into multiple files should share the same namepace (ie, map.h and map_objects.h use namespace hoa_map).
- Additionally, each hoa_ namespace may have an embedded private namespace that is to be used only within the code of the base namespace and not by other namespaces.
- The private namespace name shall be called private_ followed by the base namespace name. For example, audio.h has namespace private_audio embedded in namespace hoa_audio.
- Constants that are defined inside the base namespace (and thus are intended to be used by other parts of the code) must be prefixed with the file name. Example: constants in the file audio.h and inside namespace hoa_audio are prefixed by AUDIO_.
- Constants defined in the private embedded namespace, however, do not need to follow the above convention.
IV. Coding Style and Conventions
- Do not use spaces for indentation. Please use tabs only.
- It is not required, but recommended that you use encapsulation to hide class members from other parts of the code and make them accessible via member access functions (which can be defined in the header file for convenience).
- Use const wherever possible in function calls to ensure that data does not get modified where it shouldn't.
- When assigning floats, specify the f suffix on the number. Ex. float temp = 25.3f;
- With the exception of the above, you are free to style your code however you feel most comfortable. But please make your code style consistent throughout the code in question.
V. Forbidden Constructs
- Do not use int, unsigned char, etcetera. You may only use the following integer types, which are guaranteed to be the same size across platforms.
- int32
- uint32
- int16
- uint16
- int8
- uint8
- Do not use the C++ exception handling mechanism.
- Do not use structs. Use classes with all public members to achieve the same functionality.
- Do not use multiple inheritence.
- Typically we do not create template classes, but you are not restricted from using them if you think its necessary.
- Do not use #define for constants (there is no type-checking, and hence it is unsafe). Use const instead.
VI. Error Handling
- All functions that load data should return boolean values indicating success (true) or failure (false).
- Except as stated above, no other functions should return values to indicate success or failure.
- Each class that performs error checking (other than only loading errors) must retain some data to retain error codes.
- The error data must either be private.
- Typically, an unsigned integer is kept with each bit representing a different error code.
- The specific bit-codes should be accessible outside of the class via a series of constants made available in the class' namespace.
- Classes with error code data must provide a member access function named CheckErrors() that returns the error data.
- The CheckErrors() function resets the error code data to a no-error condition when it is called.
VII. Commenting
Header Files
Note: This guide does not go into detail about doxygen or how to use it. Please see the Doxygen homepage and manual for more information.
- Header files must be commented with doxygen style comments. These comments are used to automatically generate reference documentation for your code. (Note, however, that you may still have to provide self-written documentation, depending on the code in question.)
- The only doxygen comment required for source files is the file descriptor comment, as explained below.
- A file descriptor comment must be placed at the top of every header and source file. An example file descriptor follows below. The verbose description and notes are optional, but encouraged to be used where viable. Typically only header files need these extra comments, and you do not have to replicate the same information in the header's corresponding source file.
/*!****************************************************************************
* \file audio.h
* \author Tyler Olsen, roots@allacrost.org
* \date Last Updated: August 23rd, 2005
* \brief Header file for audio engine interface.
*
* This code provides an easy-to-use API for managing all music and sounds used
* in the game.
*
* \note This code uses the SDL_mixer 1.2.5 extension library. The documentation
* for it may be found here: http://jcatki.no-ip.org/SDL_mixer/SDL_mixer.html
*
* \note There is more functionality in SDL_mixer than what is included here, like
* stereo panning, distance attenuation, and some other effects. These
* features will be available in future releases of this code.
*****************************************************************************/
- Place your doxygen comments before the intended item you wish to comment. Please do not attempt to place your comments anywhere else.
- All namespaces, constants, functions, classes, and class members, must be commented. There are a few exceptions to this rule though.
- Constructors, destructors, and overloaded operators do not need to be commented. If they serve some special purpose or are otherwise unusual, however, you should comment them to avoid confusion.
- Macros do not need to be commented.
- TEMP_ and DEBUG_ functions may be commented when appropriate, but it is not a requirement to do so.
- Items that share the same properties can be placed in groups instead of commented individually (details on groups will follow shortly).
- Provide links to specific members in your comments whenever possible. For example:
When music is loaded, the interal MusicDescriptor#id argument is the
same value as the MusicItem#id argument in the MusicItem class.
Here, we have linked to the id argument in both the MusicDescriptor and MusicItem classes, as well as the MusicItem class itself. (Typing a class name in a comment automatically creates a link to it). - When commenting a function, please use the param and return tags to comment all parameters and the return value (if any), unless it is blantanly obvious from the function name or its description. Example:
/*!
* \brief Determines whether an object may be placed on a tile.
* \param &tcheck Container for information used to check the tile in question.
* \return True if an object may move to the tile, false otherwise.
*/
bool _TileMoveable(const private_map::TileCheck& tcheck);
- To avoid tedious and repetitive comments, structures that are very similar in purpose may be placed into groups, and only a single comment is required for the entire group. This is very useful for class member access functions and constants, which often share similar properties. It is still possible, however, to comment individual members in a group. Refer to the example below
/*! \name Input state member access functions
* \brief Returns true if the named input event key/button is currently being held down
*/
//@{
bool UpState() { return _up_state; }
bool DownState() { return _down_state; }
bool LeftState() { return _left_state; }
bool RightState() { return _right_state; }
//! This is a comment for the ConfirmState function only
bool ConfirmState() { return _confirm_state; }
bool CancelState() { return _cancel_state; }
bool MenuState() { return _menu_state; }
bool SwapState() { return _swap_state; }
bool LeftSelectState() { return _left_select_state; }
bool RightSelectState() { return _right_select_state; }
//@}
- The best way to get familiar with our header commenting style is to actually look at some of the Allacrost header files and mimic what you see there.
Source Files
- The only doxygen commenting required for source files is for the file descriptor. The only tags required are: file, author, date, and brief. Do not include a verbose description or notes, as those should already be stated in the corresponding header file. Here is an example of a source file descriptor.
/*!****************************************************************************
* \file audio.cpp
* \author Tyler Olsen, roots@allacrost.org
* \date Last Updated: August 23rd, 2005
* \brief Source file for audio engine interface.
*****************************************************************************/
- With the exception of the file descriptor, source file commenting is very lax and developers are free to comment their code anyway they see fit. Here are some useful guidelines to follow.
- Like header files, comment the last bracket of long namespaces, functions, or other blocks of code so it is clear when the construct terminates.
- Before each function, put a small but meaningful comment reminding the reader what the function does.
- Comment all local variables in functions to explain what they are used for, if its not intuitive.
- Do not over-comment. For example, count++; // increment count is not a good comment.
- Do not under-comment. Others need to be able to read your code and understand what it does without having to figure it out themselves.
- Try commenting a long section code in a series of segments and summarize what each segement does (see below for an example).
void MapMode::Update(uint32 new_time_elapsed) {
.....
// *********** (1) Update the tile animation frames ************
.....
// ****** (2) Update the map based on what state it is in ******
.....
// *** (3) Sort objects so they are in the correct draw order ***
.....
} // MapMode::Update(uint32 new_time_elapsed)
- Arrange your functions in a logical order so they are easy to find.
- Sort the source file by class definitions. That is, put all functions of class A at the top, followed by class B, etc. Do not mix class A functions with class B functions because it makes it very difficult on the reader.
- For a game mode implementation, it is recommended you put general functions (including constructors/destructors) at the top, update functions afterwards, and draw functions at the bottom.
VIII. CVS Usage Policies
Note: This section is primarily written for the Allacrost development staff.
We have a few policies in place with regards to using the official Allacrost CVS at SourceForge. The reason for these policies is to keep the CVS looking clean and to avoid clobbering someone else's commits. Information on how to use Sourceforge's CVS services can be read here and here.
- Do not commit code that does not compile, or has enough serious bugs that the game can not be run!
- If you are planning to add a new file (or directory) to the CVS, you must give the programming team advanced notice (at least a few days) and get a majority approval for the file to be added.
- The same policy above applies for removal of files or directories.
- Do not commit code that is strictly for testing purposes. Small test functions inside existing code is okay, but a file that consists only of code for testing is not acceptable. Use the FTP if you must share such test code.
- The CVS should only be used for source code, scripts, and makefile data. Media data such as images and sounds should not be included in the CVS. See this page for details on what and what not to include in the CVS.
- In general, every developer has a set of files that they are the primary owner of. Do not make major changes to another owner's files without first notifying them. Small bug fixes to others files, however, are okay to make (but you should still notify the owner of the bug you fixed).
- If you violate these policies, you may have your CVS access priveledges temporarily suspended.
IX. Rationale
This section describes the reasoning behind some of the less intutive code standard rules given above. It exists for those that are interested in why we formulated these policies, and is not required reading. ***This section is currently incomplete and will be fleshed out more fully at a later time.***
- II.D.1 and II.E.1 - Underscores for Private Class Members
It makes it much easier to discern what members and functions of a class are intended for internal use and what is available for external use by prefixing these private constructs with an underscore. Presumably, the majority of the people reading this documentation wish to actually use the engine and don't care about the inner details. With this convention, they know exactly what class members and functions they can safely ignore. - III.E. and III.E.1 - Namespace Constant Prefixing
The purpose for having a special prefix for each constant related to the namespace it comes from serves two purposes. First, it makes it really easy to know where a constant is defined when it is used elsewhere in the code. Second, it avoids naming conflicts. If I wanted to set the coordinates of the screen to be a grid of tiles in two different game mode implementations, I might accidentally name them the same TILE_ROW and TILE_COLUMNS (causing a compile error), or I might easily get confused between which constant to use in another part of the code. The private embedded namespace allows the programmer to hide constants and other data structures that other parts of the code don't need to see, and also has no naming restrictions. - IV.A - Indentation with Tabs, not Spaces
Yes, the age-old debate. The reason we use tabs is because we didn't want to have to force anyone on the programming team to adapt to an indentation width that they are uncomfortable with (ie, mandating 2 space or 4 space indentations). By using tabs, the programmer can set their IDE settings to have tabs represent the amount of spacing that they are most comfortable with. It's also easier to press tab once than press space multiple times. - IV.D - 'f' Suffix on Floating Point Numbers
Having an 'f' on the end of floating numbers is part of the ANSI standard. It also helps the compiler to distinguish the value between a float and a double. - V.A - Required Integer Types
As technology makes its transition to the 64-bit world, we can no longer rely on an int being exactly 4 bytes anymore. True, in most cases this would not matter, but in some places of the code we use bit-masked values and therefore knowing the correct size of your integer types is critical. The types were made by simplying creating typedefs for the types defined in SDL_types.h (we didn't like their convention which had the first letter of the type capitalized and used an 'S' prefix for signed integers).