Skip to content

Latest commit

 

History

History
306 lines (226 loc) · 11.3 KB

RSC-Coding-Guidelines.md

File metadata and controls

306 lines (226 loc) · 11.3 KB

Robust Services Core: Coding Guidelines

All code must >parse successfully. If it doesn't, try to use a construct that the parser understands. Failing that, enhance the parser (or ask for it to be enhanced) to support the missing language feature.

Use the >check command to help determine whether software follows recommended guidelines. It supports many of the guidelines in this document, and the >fix command can even interactively repair some of them.

The existing software does not always follow every guideline. Sometimes, there is a good reason to violate a guideline. In others, the effort that would be needed to make the software conform is better spent elsewhere. Some of the software was developed before C++11, so there are things that it should still adopt (greater use of unique_ptr, for example).

Formatting

Try to make it impossible for the reader to tell where code was added or changed.

  1. Begin each file with the following heading:
//================================================================================
//
//  <FileName.ext>
//
//  Copyright (C) 2013-2022  Greg Utas
//
//  This file is part of the Robust Services Core (RSC).
//
//  RSC 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 3 of the License, or (at your option) any later
//  version.
//
//  RSC 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 RSC.  If not, see <http://www.gnu.org/licenses/>.
  1. Use spaces instead of tabs.

  2. Indent a multiple of 3 spaces.

  3. Remove unnecessary interior spaces and semicolons. Remove trailing spaces.

  4. Use // comments instead of /*...*/.

  5. Add blank lines for readability, but do not use consecutive blank lines.

  6. Limit lines to 80 characters in length. Break after ,:) and before (. A break at an operator may occur before or after, depending on which seems to read better.

  7. Break before { and } unless everything in between also fits on the same line.

  8. Almost always use PascalCase. Use all uppercase and interior underscores only in low-level types and constants. Names that evoke Hungarian notation are an abomination.

  9. Keep * and & with the type instead of the variable (Type* t instead of Type *t).

Interfaces

  1. Insert an #include guard based on the file name (FileName.h and FILENAME_H_INCLUDED) immediately after the standard heading.

  2. Sort #include directives alphabetically within the following groups:

    1. header(s) that define base classes of classes defined in this header
    2. external headers (#include <filename>)
    3. innteral headers (#include "filename.h")
  3. Remove an #include solely associated with functions inherited from a base class.

  4. Remove an #include by forward-declaring a class that is only used with references or pointers. Use an explicit forward declaration instead of relying on this as a side effect of a friend declaration.

  5. Avoid using declarations and directives for std symbols and those of other namespaces. Prefix the namespace directly (std::<symbol>, for example).

  6. Initialize global data (static members) in the .cpp if possible.

Preprocessor

Only use the preprocessor for one of the following purposes:

  1. An #include guard.

  2. Conditional compilation (#ifdef). The symbols are defined when the compiler is launched. Those in current use are

    1. OS_WIN and OS_LINUX for Windows and Linux. These specify the target platform and may only be used in platform-specific files, which are named *.win.cpp and *.linux.cpp.
    2. FIELD_LOAD for a production build (else assumed to be a lab build). May only be used in a .cpp that executes before the configuration file is read during system initialization; otherwise use Element::RunningInLab().
    3. WORDSIZE_32 for a 32-bit CPU (else assumed to be 64-bit). May only be used in subs/ files.
    4. CT_COMPILER when running the >parse command. May only be used in subs/ files.
  3. To #define an imitation keyword that maps to an empty string. The only current examples are NO_OP and NO_FT.

Implementations

  1. Sort #include directives alphabetically within the following groups:

    1. header(s) that declare something that this .cpp defines
    2. header(s) that define direct base classes of classes defined in this .cpp
    3. external headers (#include <filename>)
    4. interal headers (#include "filename.h")
  2. Omit an #include or using that is already in the header.

  3. Put all of the code in the same namespace as the items declared in the header.

  4. Implement functions in alphabetical order, after the constructor(s) and destructor.

  5. Separate functions that belong to the same scope with a //----... that is 80 characters long.

  6. Separate classes and functions that belong to different scopes (local to the .cpp) with a //====... that is 80 characters long.

  7. Add a blank line after the fn_name that defines a function’s name for Debug::ft invocations.

  8. Name constructors and destructors "<class>.ctor" and "<class>.dtor" for Debug::ft invocations.

  9. Fix compiler warnings through level 4.

Classes

  1. Give each class its own .h and .cpp unless it is trivial, closely related to others, or private to an implementation.

  2. A base class should be abstract. Its constructor should therefore be protected.

  3. Tag a constructor explicit if it can be invoked with one argument.

  4. Make each public function non-virtual, with a one-line invocation of a virtual function if necessary.

  5. Make each virtual function private if possible, or protected if derived classes may need to invoke it.

  6. Make a base class destructor

    1. virtual and public
    2. non-virtual and protected
    3. virtual and protected, to restrict deletion
  7. If a destructor frees a resource, even automatically through a unique_ptr member, also define

    • a copy constructor: Class(const Class& that);
    • a copy assignment operator: Class& operator=(const Class& that); Here, create copies of that’s resources first, then release this’s existing resources, and finally assign the new ones.
  8. If a class allows copying, also define "move" functions. These release this’s existing resources and then take over those owned by that:

    • a move constructor: Class(Class&& that);
    • a move assignment operator: Class& operator=(Class&& that);

    The implementations typically use std::swap.

  9. Each of the above functions can be suffixed with = delete to prohibit its use, or = default to use the compiler-generated default.

  10. To prohibit stack allocation, delete the constructor and/or destructor.

  11. To prohibit scalar heap allocation, delete operator new.

  12. To prohibit vector heap allocation, delete operator new[].

  13. If a class only has static members, convert it to a namespace. If it is a class so that some members can be private, delete its constructor.

  14. Use override when overriding a function defined in a base class.

  15. Within the same level of access control, sort overrides alphabetically, and place them after functions that are not overrides.

  16. Make a function or argument const when appropriate.

  17. Remove inline as a keyword.

  18. Avoid friend where possible.

  19. Override Display if a class has data.

  20. Override Patch except in a trivial leaf class.

  21. Avoid invoking virtual functions in the same class hierarchy within constructors and destructors.

  22. Provide an implementation for a pure virtual function to highlight the bug of calling it too early during construction or too late during destruction.

  23. If a class is large, consider using the PIMPL idiom to move its private members to the .cpp.

  24. When only a subset of a class’s data should be write-protected, split it into a pair of collaborating classes that derive from Protected and Persistent (or Immutable and Permanent).

  25. Static member data begins with an uppercase letter and ends with an underscore, which may be omitted if it is not returned by a "Get" function. Non-static member data begins with a lowercase letter and ends with an underscore, which may be omitted if it is public (in which case it should probably be in a struct).

Functions

  1. Use the initialization list for constructors. Initialize members in the order that the class declared them.

  2. Use () instead of (void) for an empty argument list.

  3. Name each argument. Use the same name in the interface and the implementation, and in the base class and its overrides.

  4. Make the invocation of Debug::ft the first line in a function body, and follow it with a blank line.

  5. The fn_name passed to Debug::ft and other functions must accurately reflect the name of the invoking function.

  6. A simple "Get" function should not invoke Debug::ft unless it is virtual.

  7. Left-align the types and variable names in a long declaration list.

  8. Use nullptr instead of NULL.

  9. Check for nullptr, even when an argument is passed by reference. (A reference merely documents that nullptr is an invalid input.)

  10. After invoking delete, set a pointer to nullptr.

  11. Use unique_ptr to avoid the need for delete.

  12. Use unique_ptr so that a resource owned by a stack variable will be freed if an exception occurs.

  13. Declare loop variables inline (for auto i =, for example).

  14. Declare variables of limited scope inline, as close to where they are used as is reasonable.

  15. Use auto unless specifying the type definitely improves readability.

  16. Include {...} in all non-trivial if, for, and while statements.

  17. When there is an else, use braces in both or neither clauses of the if.

  18. When there is no else, use braces for the statement after if unless it fits on the same line.

  19. Define string constants in a common location to support future localization.

  20. To force indentation, even in the face of automated formatting, use {...} between function pairs such as

    1. EnterBlockingOperation and ExitBlockingOperation
    2. Lock and Unlock
    3. MakePreemptable and MakeUnpreemptable
  21. It is a serious bug for a function to cause an unexpected exception, so check arguments and results returned by other functions.

  22. Throw an exception only when it is impossible to fail gracefully or the work being performed must be aborted. Prefer to generate a log (Debug::SwLog) and return a failure value.

Tagged comments

Some comments identify work items. They have the form //a, where a is some character. The following are currently used:

  • //& is something in main.cpp that might be enabled for a subset build
  • //> is an internal constant that can be changed to raise or lower a limit
  • //@ is a useful breakpoint during development
  • //L is a Linux target enhancement
  • //b is a basic call enhancement
  • //c is a CodeTools enhancement
  • //d is a decoupling enhancement
  • //e is an unclassified enhancement
  • //p is a POTS enhancement
  • //x is something to be removed (should not appear in a Release)
  • //* is something to be fixed or implemented as soon as possible (should rarely appear in a Release)