Version 19 (modified by historic_bruno, 12 years ago) ( diff )

--

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) 2012 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 2012 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) 2012 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;
        }
    
        for (int i = 0; i < value; ++i)
            DoSomethingElse(i, (unsigned int)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. LOGERROR takes wprintf-style format strings. 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.
    • CStr8 is an alias for CStr. Prefer to use CStr.
  • Compare strings using == (not e.g. "a.compare(b) == 0"). Compare to literals directly (e.g. "someCStrVariable == "foo"", not "someCStrVariable == CStr("foo")").
  • 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");
    
  • In AtlasUI, you should prefer the wxWidgets API and construct strings like this (see the wxWiki for more examples):
    wxString str = _T("SomeUnicodeSTRING"); // Don't use wxT() directly because it breaks OS X build
    wxString translatedStr = _("A string that may be translated"); // Translated string for UI purposes
    
    // Sometimes you have to pass messages to the engine and need C++ strings:
    std::string cppStr = str.c_str();
    std::wstring cppWStr = str.wc_str(); // Don't use c_str() because it breaks OS X build
    

Misc

  • In header files, avoid #include and use forward declarations wherever possible.
  • Sometimes it's nice to put #includes in alphabetical order. (It's a bit prettier, and makes it easier to find if some header is already in the list or not.) (But when Foo.cpp includes Foo.h, that should usually go before every other include (except for precompiled.h which must always be first).)
  • Class names are UpperCamelCase and prefixed with C, e.g. CGameObject. Member functions are UpperCamelCase, e.g. CGameObject::SetModifiedFlag(...). Member variables are UpperCamelCase prefixed with m_, e.g. CGameObject::m_ModifiedFlag. Files are named e.g. GameObject.cpp, GameObject.h, usually with one major class per file (possibly with some other support classes in the same files). Local variables and function parameters are lowerCamelCase. Structs are treated similarly to classes but prefixed with S, e.g. SOverlayLine.
  • Write pointer/reference types with the symbol next to the type name, as in
    void example(
      int* good,
      int& good,
      int *bad,
      int &bad
    );
    
  • 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
    
  • Always check for undefined properties and/or invalid object references, if it's possible they could occur.
  • To test if a property or variable is undefined, use explicit type+value equality (===), instead of value equality (==) or typeof():
    if (someObject.foo === undefined)
      // foo is not defined
    else
      // foo is defined
    
  • In general you don't want to explicitly check for null which has a distinct, often misunderstood, meaning from undefined. A few parts of the engine return a null object reference (for example, the component system when a component is not available for the specified entity, or the GUI when a requested object was not found), you can check for valid object references easily:
    if (!cmpFoo)
      // Oh it's not a valid component, don't use it
    else
      // It is a valid component, we can use it
    

XML

  • All XML files should start with
    <?xml version="1.0" encoding="utf-8"?>
    
    and be UTF-8 encoded (preferably without a BOM but that doesn't really matter).
  • Empty-element tags should be written without a trailing space: use <foo/> or <foo bar="baz"/>, not <foo /> nor <foo bar="baz" />.
  • Indent using whatever the similar existing XML files use. (Usually two spaces; sometimes four spaces, or tabs, etc.)
Note: See TracWiki for help on using the wiki.