Code Standard

From Hero of Allacrost

Jump to: navigation, search

The purpose of this code standard is to set some common ground rules for Allacrost programmers. These rules are created to prevent bugs, to keep 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 concise as possible to keep the verbosity of this document at a minimum.

Contents

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.

  1. Header files have a .h suffix.
  2. Source files have a .cpp suffix.
  3. Do not put spaces in your filenames.
  4. Filenames should be simple and typically only one word. For example, audio.cpp for the audio engine.
  5. Regular game header and source files belong in allacrost/src/.
  6. Header and source files for engines, such as video and audio, belong in allacrost/src/engine/video/ or allacrost/src/engine/audio/.
  7. Header and source files for the various game modes, such as battle, boot, map, etc., belong in allacrost/src/modes/battle/ and so on.
  8. Header and source files for the map editor belong in allacrost/src/editor/.
  9. 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.
    1. The only exception to this rule is if all of your code is contained in an exlusive directory. For example, files in src/engine/video/ and src/editor/ can have multiple different names.
  10. To include an Allacrost header file #include "file.h". You do not need to specify a pathname.
  11. The following must be placed at the top of every Allacrost header and source file.
///////////////////////////////////////////////////////////////////////////////
//            Copyright (C) 2004-2008 by The Allacrost Project
//                         All Rights Reserved
//
// This code is licensed under the GNU GPL version 2. 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.
///////////////////////////////////////////////////////////////////////////////

Naming Conventions

The rules for naming of classes, functions, variables, members, and constants. Includes naming of special constructs, such as those used for debugging.

  • 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:
  1. All private class functions are prefixed with an underscore.
    void MapMode::_GetDrawInfo();
  2. 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();
  3. 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();
  • Variables and class members should be all lowercase letters, underscores, and numbers.
    uint32 tile_rows = 50;
  1. All private class members must be prefixed with an underscore.
    bool MapMode::_random_encounters;
  • Class member access functions should follow the following conventions, in addition to the conventions for normal functions.
  1. 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();
  1. Functions that modify the class member should be prefixed with Set
    void GameAudio::SetMusicVolume(float volume);
  2. For simple data types (booleans, integers, characters, floats, strings), the Get and Set functions should pass arguments by value and return by value.
  3. For complex data types, void should be returned and the argument should be passed by reference.
  4. 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.

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.

  1. All headers and source files must contain all of their code and constructs within a namespace.
    1. The only file that is an exception to this rule is main.cpp.
  2. The using namespace directive may only be used in source files, not header files.
  3. Namespaces must match the filename and be prefixed with hoa_. For example, audio.h has the namespace hoa_audio.
    1. Code that is split into multiple files should share the same namepace (ie, map.h and map_objects.h use namespace hoa_map).
  4. 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.
    1. 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.
  5. 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_.
    1. Constants defined in the private embedded namespace, however, do not need to follow the above convention.

Coding Style and Conventions

How to properly indent and otherwise write source code.

  1. Do not use spaces for indentation. Please use tabs only.
  2. 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).
  3. Use const wherever possible in function calls to ensure that data does not get modified where it shouldn't.
  4. When assigning floats, specify the f suffix on the number. Ex. float temp = 25.3f;
  5. 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.

Forbidden Constructs

C++ constructs that are either strictly forbidden or not typically seen in Allacrost source code.

  1. 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
  2. Do not use structs. Use classes with all public members to achieve the same functionality.
  3. Do not use multiple inheritence.
  4. Typically we do not create template classes, but you are not restricted from using them if you think its necessary.
  5. Do not use #define for constants (there is no type-checking, and hence it is unsafe). Use const instead.

Error Handling

Implementation standard for communicating detected errors with other parts of the system.

  1. All functions that load data should return a boolean value to indicate success (true) or failure (false).
  2. Except as stated above, no other functions should return values to indicate success or failure.
  3. 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.
  4. Classes with error code data must provide a member access function named CheckErrors() that returns the error data.
  5. The CheckErrors() function resets the error code data to a no-error condition when it is called.

C++ Exception Handling

Rules for using the C++ exception handling mechanism.

  1. All exceptions must be of type hoa_utils::Exception or its derived classes.
  2. Direct usage of hoa_utils::Exception is not recommended (nor is it forbidden). Use derived exceptions such as VideoException, AudioException whenever possible or if they're not available, go create a new exception class.
  3. Higher level exception handlers and catch-blocks are allowed to take plain hoa_utils::Exceptions as their parameters.
  4. When catching, always use references to Exceptions as it guarantees support for derived exceptions as well.
  5. Never use catch(...) as it does not guarantee the type of exception to be a hoa_utils::Exception.
  6. Always initialize the parameters that indicate file, line and function when exception is thrown. You can use __FILE__, __LINE__ and __FUNCTION__ macros as they're all cross-platform.
  7. Do not use exception specification lists. Instead, use doxygen comments such as // @exception outOfMemory to document the exceptions.
  8. Do not use exceptions in performance-critical areas of code. Throwing exceptions is more expensive than a try-block.
  9. Do not attempt to throw an exception inside an another one.
  10. Use exceptions only in exceptional places i.e. when something went wrong, but you cannot handle the situation in that scope of code.
  11. Never throw exceptions in destructors. Throwing in constructors is perfectly valid though.
  12. Avoid throwing by value or by pointers and avoid catching by pointer, unless an existing library requires so.

Commenting

Strict guidelines on how to satisfactorly comment both header and source files.

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.
  1. 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.
  2. 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
*** \brief   Header file for audio engine interface.
***
*** This code provides an easy-to-use API for managing all music and sounds data
*** used throughout the game.
***
*** \note This code uses the following libraries:
***   -# OpenAL, http://www.openal.org/
***   -# ALUT, http://www.openal.org/
***   -# Ogg Vorbis, http://www.vorbis.com/
******************************************************************************/
  1. 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).
  2. Provide links to specific members in your comments whenever possible. For example:
/** When music is loaded, the internal 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).

  1. When commenting a function, please use the param and return tags to comment all parameters and the return value (if any), unless it is blantantly obvious from the function name or its description. Example:
/** \brief Determines whether an object may be placed on a tile.
*** \param &tcheck A reference used to check the properties of the tile in question.
*** \return True if an object may move to the tile, false otherwise.
**/
bool _TileMoveable(const private_map::TileCheck& tcheck);
  1. 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; }
//@}
  1. 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
*** \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.
    1. Like header files, comment the last bracket of long namespaces, functions, or other blocks of code so it is clear when the construct terminates.
    2. Before each function, put a small but meaningful comment reminding the reader what the function does.
    3. Comment all local variables in functions to explain what they are used for, if its not intuitive.
    4. Do not over-comment. For example, count++; // increment count is not a good comment.
    5. 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.
    6. 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)

    1. Arrange your functions in a logical order so they are easy to find.
      1. 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.
      2. 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.

Lua Coding Style

A small standard that pertains only to Lua code, not C++.

  • The following must be placed at the top of every Allacrost Lua file.
--[[ --------------------------------------------------------------------------
--            Copyright (C) 2004-2006 by The Allacrost Project
--                         All Rights Reserved
--
-- This code is licensed under the GNU GPL version 2. 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.
--]] --------------------------------------------------------------------------
  • Use tabs for indentation


Rationale

Verbosely explains the reasoning behind many of the code standards set in previous sections.

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.C - Enum Values

Ordinarily, enumerators will start at 0 for the first symbolic constant, 1 for the next, and so on. However, an enumeration type can acquire an integer value that may not be any of the enumerators listed in or implied by the enumeration definition. Assigning enumerators specific values and defining INVALID and TOTAL symbolic constants prevents the programmer from committing mistakes.

  • 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).

Personal tools