Version 8 (modified by Philip Taylor, 12 years ago) ( diff )

more conventions

Introduction

The goal of these coding conventions is to encourage consistency within the code, rather than claiming some particular style is better than any other. As such, the main guideline is:

  • When modifying a piece of code, try to follow its existing style. In particular:
    • Primarily, try to match the style of the functions that you're editing (assuming it's at least self-consistent and not too bizarre), in order to avoid making it less self-consistent.
    • Secondly, try to match the style of the files that you're editing.
    • Then, try to match the style of the other code in the subdirectory you're editing.
    • Finally, try to match the global guidelines discussed on this page.

Our code is currently not entirely consistent in places, but the following guidelines attempt to describe the most common style and are what we should converge towards. (Obviously we always want clean, readable, adequately-documented code - lots of articles and books already talk about how to do that - so here we're mostly describing minor details.)

C++

Creating new files

  • All source files (.cpp, .h) must start with the following GPL license header, before any other content:
    /* Copyright (C) 2011 Wildfire Games.
     * This file is part of 0 A.D.
     *
     * 0 A.D. is free software: you can redistribute it and/or modify
     * it under the terms of the GNU General Public License as published by
     * the Free Software Foundation, either version 2 of the License, or
     * (at your option) any later version.
     *
     * 0 A.D. is distributed in the hope that it will be useful,
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     * GNU General Public License for more details.
     *
     * You should have received a copy of the GNU General Public License
     * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     */
    
    replacing 2011 with the year that the file was last updated.

Exception: Code in source/lib/ (and a few other files) should use the MIT license instead:

/* Copyright (c) 2011 Wildfire Games
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
  • Wrap header files in include guards, using the name INCLUDED_filename, e.g. the file Foo.h should say:
    #ifndef INCLUDED_FOO
    #define INCLUDED_FOO
    ...
    #endif // INCLUDED_FOO
    
  • All source files must have the svn:eol-style property set to native
  • The first non-comment line of any source file must be #include "precompiled.h"

Formatting

  • Use tabs for indentation, not spaces.
  • For any alignment within a line of code (as opposed to indentation at the start), use spaces, not tabs.
  • Indent braces and use whitespace like
    int CExampleObject::DoSomething(int value)
    {
        if (value != 0)
        {
            Prepare();
            m_Value = value;
        }
        return value;
    }
    
    Exception: Code in source/lib/ omits the space before the '(' in statements like "if(...)", "while(...)", etc.
  • Try to avoid very wide lines. Typical wrapping points are 80 characters, or 120, or 132, etc. (There's no strict limit - aim for whatever seems most readable.)
  • Write switch statements like
    switch (n)
    {
    case 10:
        return 1;
    
    case 20:
        foo();
        // fall through to next case    [this should be explicit if you don't end with break or return]
    
    case 30:
        bar();
        break;
    
    case 40:
    {
        int i = n*2;       // [need the extra {...} scope when declaring variables inside the case]
        return i;
    }
    
    default:
        debug_warn(L"invalid value for n");   // [only do this kind of warning if this case is an engine bug]
    }
    
    

Error reporting

  • For engine bugs (that is, error cases which need to be fixed by C++ developers, and which should never be triggered by users or modders (even if they write invalid data files)), use "debug_warn(L"message")" to report the error. (This pops up an ugly dialog box with stack trace and continue/debug/exit buttons.)
  • For error cases that could be triggered by modders (e.g. invalid data files), use
    LOGERROR(L"Failed to load item %d from file %ls", i, path.c_str());
    
    This gets display on screen in red, and saved in the log files. Exception: Code in source/lib/ can't use LOGERROR (it can only use things defined in source/lib/).
  • The engine should try to cope gracefully with LOGERROR cases, e.g. abort loading the current file; it should never crash in those cases.

Documentation

  • Use Doxygen comments (explained here as JavaDoc style), e.g.
    /**
     * A dull object for demonstrating comment syntax.
     */
    class CExampleObject
    {
        /**
         * Sets the object's current value to the passed value, if it's non-zero.
         *
         * @param v the new value to set it to, or 0 to do nothing.
         *
         * @return the value that was passed in.
         */
        int DoSomething(int v);
    
        /// Current value (always non-zero)
        int m_Value;
    };
    
  • Try not to repeat class names or function names in the descriptions, since that's redundant information.
  • Don't need to bother documenting every line of code or every member function or every member variable; only when it'll add to a competent reader's understanding of the program.

Strings

  • Use CStr instead of std::string. Use CStrW instead of std::wstring. (These are subclasses of std::[w]string with various extra methods added for convenience.)
    • Exception: source/lib/ and source/simulation2/ and source/scriptinterface/ tend to prefer std::[w]string instead.
  • For portability, use the following formats for printf-style functions:
    printf("%s", "char string");
    printf("%ls", L"wchar_t string");
    wprintf(L"%hs", "char string");
    wprintf(L"%ls", L"wchar_t string");
    

Misc

  • In header files, avoid #include and use forward declarations wherever possible.
  • Sometimes it's nice to put #includes in alphabetical order.
  • Class names are CamelCase and prefixed with C, e.g. CGameObject. Member functions are CamelCase, e.g. CGameObject::SetModifiedFlag(...). Member variables are CamelCase prefixed with m_, e.g. CGameObject::m_ModifiedFlag. Files are named GameObject.cpp, GameObject.h, usually with one major class per file (possibly with some other support classes in the same files).
  • Use STL when appropriate.
  • Don't use RTTI (dynamic_cast etc). Exception: source/tools/atlas/AtlasUI/ can use RTTI.
  • Avoid global state: global variables, static variables inside functions or inside classes, and singletons.
    • When a module needs access to objects from outside its own environment, prefer to pass them in explicitly as arguments when instantiating that module, rather than making the objects global and having the module reach out to grab them.
    • When unavoidable, global variables should be named with a g_ prefix.
    • Prefer global variables over singletons, because then they're not trying to hide their ugliness.
  • Don't do "if (p) delete p;". (That's redundant since "delete NULL;" is safe and does nothing.)
  • If deleting a pointer, and it's not in a destructor, and it's not being immediately assigned a new value, use "SAFE_DELETE(p)" (which is equivalent to "delete p; p = NULL;") to avoid dangling pointers to deleted memory.

JavaScript

  • Use the same basic formatting as described above for C++.
  • Use roughly the same Doxygen-style comments for documentation as described above for C++. (But we don't actually run Doxygen on the JS code, so there's no need to make the comments use technically correct Doxygen syntax.)
  • Don't omit the optional semicolons after statements.
  • Use quotes around the key names in object literals:
    var x = 100, y = 200;
    var pos = { "x": x, "y": y };
    
  • Create empty arrays and objects with "[]" and "{}" respectively, not with "new Array()" and "new Object()".
  • To convert a string to a number, use the "+" prefix operator (not e.g. parseInt/parseFloat):
    var a = "1";
    var b = a + 1;     // string concatenation; b == "11"
    var c = (+a) + 1;  // numeric addition; c == 2
    
Note: See TracWiki for help on using the wiki.