coding_conventions - ryzom/ryzomcore GitHub Wiki


title: Coding Conventions description: published: true date: 2023-03-01T05:04:02.337Z tags: editor: markdown dateCreated: 2019-11-25T02:40:20.851Z

This document defines and formalizes the presentation and redaction of all source files. A strict adherence to these conventions is required to ensure a readable and understandable code.

File Naming

There are very few restrictions on file names and locations. The following rules are expected:

  • Files must end with the appropriate suffix:
    • cpp for C++ code
    • c for C code
    • h for included declarations
  • Significant, English, file names. Length is not capped but should be kept within reasonable bounds.
  • No spaces or uppercase letters. If the name is composed of separate, discrete, words the underline character is used instead of a space as the separator.

File Locations

A quick check of the source directory will show how files are organized within the source tree. The sources are organized in two levels.

The first level specifies which part of the platform the source belongs to:

  • nel
  • nelns
  • tool
  • snowballs2
  • ryzom

The second level specifies the types of data:

  • include
  • src
  • samples

or

  • client
  • tools
  • server

The directory include only contains public headers, if you have private headers put them in src. {.is-warning}

The third level specifies which logical part the file belongs to:

  • 3d
  • misc
  • net

This applies to basic source as well as include files, who are in a separate include tree and never along their source code files. Thus, an include declaration would be:

#include "nel/misc/steam.h" 

File Header

All code files must begin with a specific header. The model of that header is defined in headers.txt in the documentation directory. It is recommended that people include that file immediately when starting a new file.

Basic Types

Integral Types

  • The first letter specifies s for signed integers or u for unsigned integers. It is not possible to specify a dont care sign integer.
  • The next three letters are int.
  • The type ends with the number of bits used to represent the integral type, or nothing to specify an integer that is at least 32 bits wide.

Examples: uint8 (unsigned 8 bit integer), sint64 (signed 64 bit integer), sint (signed integer of at least 32 bits) {.is-info}

Classic Types

  • float (32 bits floating point number, use is discouraged)
  • double (64 bits floating point number)
  • bool (an equivalent to the hypothetical uint1 type)
  • void (empty type)
  • char (UTF-8 byte, not always a whole code point, not to be confused with uint8/sint8)
  • ucchar (UTF-16 byte, not always a whole code point, deprecated)
  • u32char (UTF-32 code point, not always equivalent to a whole character glyph)
  • string (UTF-8 encoded string)
  • ucstring (UTF-16 encoded string, deprecated, inherited from std::basic_string)
  • u32string (UTF-32 code point string, inherited from std::basic_string)

As a general rule, the classic and sloppy use of short / int / long is proscribed, as is using a char to represent a 8-bit number, unless this is required to call an external API. a char should use only when you want to represent a UTF-8 character.

Naming Convention

All elements will be in their own namespace, which is short, in all uppercase, and consist of a two letter code (NL for NeL), followed by the directory name. Thus an element of the NeL 3D library will be declared in the NL3D namespace.

The following conventions are applied to names within these namespaces:

  • All names must include one or more english words which have relevance to the entity being named. If multiple words are used for a more descriptive naming, all words begin with an uppercase letter, the underscore character is not used to separate words (e.g. Word, FullWord, ComplexQualifiedWord)
  • Common abbreviations may be fully capitalized. However, abbreviations should not be separated by underscores (e.g. IOBuffer, io_buffer.h)
  • Class methods start with a lowercase character, e.g. load(), store(), giveMoney()
  • Private methods are prefixed with p_, e.g. p_refreshPosition()
  • Public attributes of a class start with an uppercase, e.g. Layer, Set, ResourceType
  • Private or protected member variables of a class are prefixed with m_, e.g. m_Driver (using _ as a prefix is outdated, and prohibited)
  • Local variables and parameters start with a lowercase letter, e.g. size, x, i, meshInstance
  • Structures and Classes start with the uppercase C letter, then the name starting with an uppercase letter as well, e.g. CTexture, CMyClass
  • Interfaces start with the uppercase I letter, then the name starting with an uppercase letter as well
  • Iterators start with the prefix It (uppercase I, lowercase t), then the name starting with an uppercase letter, e.g. ItTexture
  • Other types, enums and the like are prefixed with an uppercase T, then the name.
  • Globals function and values are prefixed by their namespace values, then follow the standard conventions, e.g. NLMISC::createAttribute()
  • Exported C symbols are prefixed by their namespace with an underscore replacing double colons, then following the same conventions, e.g. NL3D_libraryVersion(), NL3D_UDriver_create()
  • Preprocessor constants are in full uppercase, and consist of the namespace, an underscore character, then a name, with underscores as separators, e.g. NLNET_MACRO, NL_OS_UNIX

Note that there is no difference between variables and constants.

Coding Style

Indentation

A strong suggestion is to set your tabulation marks to 4 characters, rather than the usual 8. Indentation is always done using tabulations, never with spaces. Make sure your text editor does not replace tabulations with spaces.

Avoid aligning variable types and names, as well as any other unnecessary alignments. If it is needed to align code for readability (e.g. structured constant data), use spaces instead of tabs for strict alignment, and surround the code with guards to turn off any auto-formatting.

uint8 data[] = {
// clang-format off
      0,   1,   2,   4
     12,  14,  16,  18
    204, 208, 212, 216
// clang-format on
};

Braces

Source code is written using a flat style. In this style, the opening and closing curly braces are placed on separate lines (never with code), and at the same indentation as the preceding defining element, while the content of the block enclosed between the braces is indented once from the source. Your code source should look like this:

class CDummy
{
    void dummy(uint32 b)
    {
        if (b == 0xDEADBEEF)
        {
            // ...
        }
    }
};

Whitespace

  • Spaces before opening brackets and after closing brackets, only when preceeded or followed by an operator or keyword.
  • No spaces between multiple opening brackets. No spaces between multiple closing brackets.
  • No spaces after an opening bracket. No spaces before a closing bracket.
  • No spaces between empty brackets, except space between empty curly brackets.
  • Space after a comma, no space before a comma. Space after a semicolon, no space before a semicolon.
  • No space between unary operators and literals or variables.
  • Space between binary operators and literals or variables.
  • No spaces surrounding dot and arrow operators.
  • Space surrounding arrow symbol for lambda.
  • No space before the round starting bracket of a function call.
  • No whitespace after the last character on a line.
  • Indentation for definitions goes after the hash.
  • Pointer and reference marker belong to the variable name, so no space between them either.
  • Scope opening brackets go on a new line.
  • Opening brackets that are part of an expression, such as an array or lambda, do not go on a new line.
#ifdef NL_OS_WINDOWS
#  define NL3D_FIX_BUG
#endif
void function(const char *str, const std::string &s, char *const buffer)
{
    if (str[0] && s.c_str()[0])
    {
        auto lambda = [b = buffer]() -> void {
            b[0] = (1 + b[0]);
        });
    }
    char *stringArray[] = {
        "Hello",
        "World"
    };
    int numberArray[] = { 0, 1, 2, 3 };
    std::vector<int> vec;
    vec.push_back(std::min<int>(numberArray[0], 50));
}

Comments

Comments may use either the C++ style comments //, or the more classic C commenting method. If possible the source must be heavily commented and as many comments as possible must conform to the Doxygen commenting methods so that they can be extracted and reused as software documentation.

Comment "separators" will be made with a line of repeated star '*' characters. You should not use more than 50 stars to separate.

The practice of comment blocks, comments where each line begins and end with a star character, to create a "text block" is proscribed.

Final Coding Recommendations

Even if most of these are obvious recommendations, it is useful to restate these basic principles:

  • The use of #define must be limited. Constants values and macro functions can be expressed directly in C++ using constants and inline functions, and do not require pre-processor definitions.
  • The using namespace declaration is proscribed in include files.
  • Include files paths, and all other file names should use the '/' character as the directory separator, for portability purposes.
  • Use C++ classes, like std::string instead of old school char * C when storing strings.
  • Use the const keyword as much as possible.

Naming Guidelines

Getters and setters

Variable names must not be verbs. Do not include get as part of the getter function name. Note that while "size" can technically be used as a verb, it is not commonly used as such in programming, as the verb "resize" is more commonly used for such operation. Depending on the context this may vary. Getters and setters must return immediately, otherwise use a different pattern.

size_t size(); // Okay
void setSize(size_t size); // Okay
void size(size_t size); // Not okay. Ambigious usage of size as a verb
void resize(size_t size); // Not okay as a simple setter, implies a complex resizing operation

Booleans

Boolean variable names and functions that calculate a boolean must not be verbs, but may be past tense verbes. This in order to avoid getters that sound like functions. Names must also not imply another variable type. Functions which merely return a boolean as success result do not apply to this rule.

bool m_CallParent; // Not okay. Getter will conflict with function bool callParent()
bool m_ParentCalled; // Okay, but ambigious whether a return status or an expectation
bool m_PlayVideo; // Not okay. Getter will conflict with function bool playVideo()
bool m_VideoPlayed; // Okay, but ambigious whether a return status or an expectation
bool m_PlayedVideo; // Okay, but ambigious whether a return status or an expectation
bool m_Rectangular; // Okay
bool m_Rectangle; // Not okay, implies a Rectangle value type rather than a bool

The following verbs: is, must, can, will, has, and should, however, are allowed and recommended only for booleans, and functions that return boolean variables. These should therefore, also not be used for non-boolean returning functions or variables. Verbs should be omitted if there is no chance for ambiguity.

bool m_HasCalledParent; // Okay
bool m_ShouldCallParent; // Okay
bool m_MustCallParent; // Okay
bool m_IsRectangular; // Not so okay, prefer m_Rectangular
bool m_IsRectangle; // Okay, may be used to imply that a variant is of type Rectangle, prefer m_Rectangular when referring to the shape

Choosing between whether a noun or verb is first, depends on the context.

bool m_ShouldPlayVideo;
bool m_ShouldPlayAudio;
bool m_ShouldPlayGame;

bool m_HasVideoPlayed;
bool m_HasVideoPaused;
bool m_HasVideoStopped;

Words ending with -able are prefered names for booleans over using verbs.

bool m_SongPlayable; // Okay
bool m_CanPlaySong; // Not prefered, prefer to use m_SongPlayable instead
bool m_PlayableSong; // Not okay, would refer to the playable song rather than the state of being playable

Boolean names may be states of being. These are generally words that can be placed in "I am ..." and "It is ...". In most cases these can possibly start with Is, and it is preferred to omit the Is as stated previously if there is no ambiguity.

bool m_Active;
bool m_Rectangular
bool m_Happy;
bool m_Large, m_Small, m_Effortless;
bool m_Amazing, m_Perfect, m_Whole;
bool m_InLove;

Some of these may be verbs, this is okay too.

bool m_Enabled; // Past tense of 'to enable'

Implied function performance cost

Function names should clearly identify when they take a long time to process. Recommend using verbs such as calculate, process, generate, build, and so on.

int size(); // Return size immediately
int count(); // Counts number of elements, may take a short while
int calculateSize(); // Takes some time to calculate the size
int identifier(); // Immediate result
int lookupIdentifier(); // Relatively fast lookup in a lookup structure
int findIdentifier(); // Slow search that isn't optimized
⚠️ **GitHub.com Fallback** ⚠️