Coding Style Guide

The following is a set of coding style rules which all GUF code follows. Some of the rules are more important than others, but the most important rule is to be consistent. Even if a rule seems totally irrelevant (like where you place braces surrounding a code block), consistently following the rule will make your code easier to read and understand for other programmers.

Basics
  • Symbol Naming
    • Types
      • Type names always start with a capital letter. The first letter of each word in the name is also capitalized. All other letters are lower-case. Type names do not contain underscores.
      • If the type is at global scope (as opposed to being inside another class), the type name is prefixed with a capital letter which indicates the software's name. For example, classes in GUF are prefixed with 'G'. This is done even if the class is in a namespace.
      • No distinction is made between classes, structs, unions, and typedef'd types. This is intentional.
      • If the class is a smart pointer type, the name is the type which it points to followed by "Ptr". So, a smart pointer to a GObject is a GObjectPtr.
      • If the class is an exception type, the name ends with "Exception".
    • Variables (including function arguments)
      • Variable names always start with a lower-case letter. The first letter of each word in the name is capitalized unless that letter is the first letter of the variable name. All other letters are lower-case. Variable names do not contain underscores.
      • If the variable is at class-scope, it is prefixed with 'm'. If it is at global scope, it is prefixed with 'g'. When a variable name contains either prefix, the second letter of the name is capitalized.
    • Functions
      • Function names always start with a capital letter. The first letter of each word in the name is also capitalized. All other letters are lower-case. Function names do not contain underscores.
    • Constants
      • Constant names contain only capital letters and use underscores to separate words.
    • Macros
      • Macro names contain only capital letters and use underscores to separate words.
      • Macro names are prefixed with the name of the software package which they are defined in. For example, GUF macros are prefixed with "GUF_". This is to prevent namespace conflicts, which are especially problematic with macros because macros ignore scoping.
    • Namespaces
      • Namespace names contain only lower-case letters. They contain only one word, so underscores are not used.
  • {} brackets enclosing code blocks are written on separate lines. They are indented to the level of the block they are in, not the block they contain.
    Example:
        if(condition)
        {
            DoSomething();
            DoSomethingElse();
        }
    
  • Indentation is done using tabs, not spaces. Code is written to be independent of tab size. This way, each reader can use their own prefered indentation size.
  • When large amounts of code in adjacent lines mach, whitespace is added to the code to make the matching parts line up. This helps make it clear to the reader that the adjacent lines are doing similar things.
Scoping
  • Global functions and variables are not used. All functions and variables must be contained in classes. The only exception is overloaded operators.
  • Use of macros is avoided when possible because macros do not obey scoping rules.
  • Everything is contained in some form of namespace. The top-level namespace indicates the name of the software product. For example, all code in GUF resides in the top-level namespace "guf". Similarily, all code in the C++ standard library resides in the namespace "std".
  • Sub-namespaces are used to distinguish individual packages in the software. Packages can contain sub-packages to any level.
Physical Layout
  • Modules
    • Each module consists of one main class. Any other classes that are part of the module must be contained within the main class.
    • Each module contains two source files:
      1. A header file defining the interface to the class. This file is nameded after the class itself, which the file extension ".h". The file contains an include guard to prevent problems if the header is included twice. The include guard is a macro containing the complete namespace and class name of the module, separated by underscores, and suffixed with "__INCLUDED__". For example, guf::xml::GXmlParser's header is named "GXmlParser.h" and looks something like this: #ifndef guf_xml_GXmlParser__INCLUDED__ #define guf_xml_GXmlParser__INCLUDED__ //insert interface here #endif //included
      2. A source file defining the class implementation (if any). This should be named after the class itself, with the file extension ".cpp".
  • Packages
    • Each package consists of any number of modules and sub-packages.
    • Packages are contained in directories named after the package.
    • Package names follow the same rules as namespace names, since namespaces are used to indicate packages.
  • Compiled libraries
    • Any module or package can be compiled into a library. The library is named after the module or package with the complete scope indicated, separated by underscores. The library is suffixed with a file type extension dependent on the operating system. Some systems also specify a prefix. For example, if you were to compile GXmlParser into a library on Linux, the name of the library would be "libguf_xml_GXmlParser.so". On Windows, it would be "guf_xml_GXmlParser.dll". However, normally you would not make a library from a single class. In this case, you would probably compile GUF and all sub-packages as one large library called "libguf.so" or "guf.dll".
Logical Layout
  • Each class is either pass-by-value, pass-by-smart-pointer, or pass-by-exception.
    • Pass-by-value classes usually represent custom data types.
      • All member variables are defined in the class's header.
      • The class contains a copy constructor and a copy assignment operator. These operators should be short enough to be inlined. Under no circumstances should these operators do anything excessively time-consuming, like allocating memory.
      • The class may not inherit from any other class and may not contain any virtual functions. No other class may inherit from a pass-by-value class.
      • When trying to decide whether to pass an object by value or by reference, ask yourself, "If this were an integer, what would I do?" If you would pass the integer by value, pass the object by value. Generally, this means that you should never find yourself passing const references around, because you should be passing by value instead.
    • Pass-by-smart-pointer classes represent larger objects.
      • The classes never have a legal copy constructor or assignment operator.
      • These classes are always derived from guf::GObject to allow smart pointers to point at them.
      • The class name with "Ptr" appended to the end should be typedef'ed as a smart pointer to the class. For example, if you include the headers which define guf::xml::GXmlParser, then guf::xml::GXmlParserPtr will also be defined as a smart pointer to GXmlParser.
      • Only smart pointers may point at these classes. Regular pointers are never used with these as they could cause memory leaks and dangling pointer problems.
      • The classes never have a legal constructor of any kind. Since these classes must only have smart pointers pointing to them, you must call a static function of the class to allocate one.
      • Usually, the headers for pass-by-smart-pointer classes define only an abstract interface (pure virtual) for the class. This interface then contains a single static function called "New" which allocates and returns a new instance of the class. The actuall class is defined in the acompanying implementation file. This insures that the implementation can be changed at will without breaking binary compatibility with previous versions.
      • Many of these classes are no more that abstract interfaces, and don't have a New function.
    • Pass-by-exception classes are thrown as exceptions.
      • Exception classes are almost always defined within the scope of another class. It usually doesn't make sense to have an exception as a separate module, after all.
      • All user-defined exceptions are derived from guf::GException.
      • Exception classes must be pass-by-value safe. C++ requires this.
Exceptions
  • Almost all functions have an exception specification. The only ones that don't are inline functions and destructors, both of which are assumed to never throw exceptions. Remember, calling a function with an exception specification is, in fact, slower than calling one without.
  • The C++ standard exceptions may be used, but use them sparingly. Normally, bad_alloc is the only one of these that sees much use.
  • There should be one exception class for each type of error that might occur. For example, a function which opens a file should be able to throw both "FileNotFoundException" and "AccessDeniedException" rather than having just one "FileIOException". Better yet, both of the specific exceptions should be derived from the general "FileIOException". This way, the caller can catch the different exceptions individually and treat them differently, or can catch all errors at once if preferred.
       Some types of exceptions may also contain data. For example, an exception thrown by an XML parser when a syntax error is found might contain the line number and column number of the document where the error occurred.
  • Be careful when deciding what should throw an exception. Here are a few guidelines:
    • Exceptions should only be thrown in conditions that would not normally happen. A program should be able to run normally without any exceptions being thrown.
    • If a program has to call a function specifically to see if it throws an exception, something is wrong. In these cases, a function should be provided specifically to check for the exceptional condition without actually raising any exceptions. For example, a function which opens a file should throw an exception if the file is not found. However, some programs may want to explicitly check to see if a file exists. In this case, the program should be able to call a function specifically written for this purpose. The function would simply check for the existance of the file and return true or false.
         Do not take this rule too far, however. Not all exceptional conditions need to be explicitly checkable. For example, an XML parser might throw an exception when it finds a syntax error in a document. However, providing a specific function which checks for syntax errors would probably not be useful. Use your judgement.
    • Remember the key advantage of exceptions. Exceptions are better than returning error codes because they force the user to check the error. Bugs caused by unchecked return values in languages like C can be painful to track down, whereas an uncaught exception can easily be tracked down by catching it higher up in the stack and querying it.
         However, the fact that exceptions must be caught can also be a disadvantage. If the exceptional condition is one which the caller could safely ignore, then think carefully about whether to throw. If you throw the exception, the caller will not be able to ignore it. If everything throws exceptions all the time, then things will become frustrating very quickly.
  • Remember, like any C++ language feature, exceptions can be very useful, but they can also be very frustrating if used wrong.